diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/NoiseTexture.png b/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/NoiseTexture.png new file mode 100644 index 00000000000..067f8d76960 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/NoiseTexture.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/TileTexture.png b/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/TileTexture.png new file mode 100644 index 00000000000..8df8ddc2c21 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/Assets/BrushAssets/TileTexture.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index d04d0deac93..97a88f3487d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -135,6 +135,8 @@ + + @@ -269,8 +271,11 @@ + + + @@ -511,6 +516,9 @@ + + TilesBrushPage.xaml + EyedropperPage.xaml @@ -529,6 +537,12 @@ OnDevicePage.xaml + + AcrylicBrushPage.xaml + + + PipelineBrushPage.xaml + RemoteDeviceHelperPage.xaml @@ -584,9 +598,12 @@ + + + + - @@ -979,6 +996,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile @@ -1006,6 +1027,14 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrush.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrush.png new file mode 100644 index 00000000000..a40e933b133 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrush.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml new file mode 100644 index 00000000000..d13797dabca --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml.cs new file mode 100644 index 00000000000..742287ddd81 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushPage.xaml.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the acrylic brushe. + /// + public sealed partial class AcrylicBrushPage : Page + { + /// + /// Initializes a new instance of the class. + /// + public AcrylicBrushPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushXaml.bind new file mode 100644 index 00000000000..40ffce65d83 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AcrylicBrush/AcrylicBrushXaml.bind @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrush.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrush.png new file mode 100644 index 00000000000..eb779d49781 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrush.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushCode.bind new file mode 100644 index 00000000000..9b31cb8e4e3 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushCode.bind @@ -0,0 +1,20 @@ +// Defines a brush like the one shown in the XAML code, but via C# code. +using Microsoft.Toolkit.Uwp.UI.Media; +using Microsoft.Toolkit.Uwp.UI.Media.Effects; +using Windows.UI.Xaml.Media; + +Brush brush = + PipelineBuilder + .FromBackdrop() + .LuminanceToAlpha() + .Opacity(0.4f) + .Blend( + PipelineBuilder.FromBackdrop(), + BlendEffectMode.Multiply) + .Blur(16) + .Shade("#FF222222".ToColor(), 0.4f) + .Blend( + PipelineBuilder.FromTiles("/Assets/BrushAssets/NoiseTexture.png"), + BlendEffectMode.Overlay, + Placement.Background) + .AsBrush(); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml new file mode 100644 index 00000000000..7584815b8cc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml.cs new file mode 100644 index 00000000000..a28b89b3168 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the pipeline brushes. + /// + public sealed partial class PipelineBrushPage : Page + { + /// + /// Initializes a new instance of the class. + /// + public PipelineBrushPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind new file mode 100644 index 00000000000..6b3083b4698 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrush.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrush.png new file mode 100644 index 00000000000..b3e61ce0651 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrush.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml new file mode 100644 index 00000000000..923e1cfaa40 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml.cs new file mode 100644 index 00000000000..11a0fe79e82 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushPage.xaml.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the tiles brushe. + /// + public sealed partial class TilesBrushPage : Page + { + /// + /// Initializes a new instance of the class. + /// + public TilesBrushPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushXaml.bind new file mode 100644 index 00000000000..3d7fa0e5e4a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TilesBrush/TilesBrushXaml.bind @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index ae9142c545f..f246c5b366c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -1037,10 +1037,9 @@ "Name": "BackdropBlurBrush", "Type": "BackdropBlurBrushPage", "About": "Brush which fills the contents with a blurred version of whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/BackdropBlurBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs", "XamlCodeFile": "BackdropBlurBrushXaml.bind", "Icon": "/SamplePages/BackdropBlurBrush/BackdropBlurBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/BackdropBlurBrush.md" }, @@ -1048,10 +1047,9 @@ "Name": "BackdropInvertBrush", "Type": "BackdropInvertBrushPage", "About": "Brush which fills the contents with an inverted version of whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/BackdropInvertBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs", "XamlCodeFile": "BackdropInvertBrushXaml.bind", "Icon": "/SamplePages/BackdropInvertBrush/BackdropInvertBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/BackdropInvertBrush.md" }, @@ -1059,10 +1057,9 @@ "Name": "BackdropGammaTransferBrush", "Type": "BackdropGammaTransferBrushPage", "About": "Brush which fills the contents with a gamma modified version of whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/BackdropGammaTransferBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropGammaTransferBrush.cs", "XamlCodeFile": "BackdropGammaTransferBrushXaml.bind", "Icon": "/SamplePages/BackdropGammaTransferBrush/BackdropGammaTransferBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/BackdropGammaTransferBrush.md" }, @@ -1070,10 +1067,9 @@ "Name": "BackdropSaturationBrush", "Type": "BackdropSaturationBrushPage", "About": "Brush which applies a Saturation effect to whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/BackdropSaturationBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs", "XamlCodeFile": "BackdropSaturationBrushXaml.bind", "Icon": "/SamplePages/BackdropSaturationBrush/BackdropSaturationBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/BackdropSaturationBrush.md" }, @@ -1081,10 +1077,9 @@ "Name": "BackdropSepiaBrush", "Type": "BackdropSepiaBrushPage", "About": "Brush which applies a Sepia effect to whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/BackdropSepiaBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs", "XamlCodeFile": "BackdropSepiaBrushXaml.bind", "Icon": "/SamplePages/BackdropSepiaBrush/BackdropSepiaBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/BackdropSepiaBrush.md" }, @@ -1092,10 +1087,9 @@ "Name": "ImageBlendBrush", "Type": "ImageBlendBrushPage", "About": "Brush which applies a blending effect a given image from whatever's behind it.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/ImageBlendBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs", "XamlCodeFile": "ImageBlendBrushXaml.bind", "Icon": "/SamplePages/ImageBlendBrush/ImageBlendBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/ImageBlendBrush.md" }, @@ -1103,12 +1097,42 @@ "Name": "RadialGradientBrush", "Type": "RadialGradientBrushPage", "About": "A composition brush which creates a radial gradient effect.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Media/RadialGradientBrush.cs", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.cs", "XamlCodeFile": "RadialGradientBrushXaml.bind", "Icon": "/SamplePages/RadialGradientBrush/RadialGradientBrush.png", - "BadgeUpdateVersionRequired": "Creators Update required", "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/RadialGradientBrush.md" + }, + { + "Name": "PipelineBrush", + "Type": "PipelineBrushPage", + "About": "A composition brush which can render any custom Win2D/Composition effects chain.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs", + "CodeFile": "PipelineBrushCode.bind", + "XamlCodeFile": "PipelineBrushXaml.bind", + "Icon": "/SamplePages/PipelineBrush/PipelineBrush.png", + "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/PipelineBrush.md" + }, + { + "Name": "AcrylicBrush", + "Type": "AcrylicBrushPage", + "About": "A composition brush that provides a custom implementation of the official acrylic brush.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs", + "XamlCodeFile": "AcrylicBrushXaml.bind", + "Icon": "/SamplePages/AcrylicBrush/AcrylicBrush.png", + "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/AcrylicBrush.md" + }, + { + "Name": "TilesBrush", + "Type": "TilesBrushPage", + "About": "A composition brush that provides the ability to display a repeated image on its render surface.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs", + "XamlCodeFile": "TilesBrushXaml.bind", + "Icon": "/SamplePages/TilesBrush/TilesBrush.png", + "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/TilesBrush.md" } ] }, diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs b/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs index 53216f50c84..8de05e2055f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs @@ -8,6 +8,7 @@ using Microsoft.Graphics.Canvas.Text; using Microsoft.Graphics.Canvas.UI.Composition; using Windows.Foundation; +using Windows.Foundation.Metadata; using Windows.Graphics.DirectX; using Windows.UI; using Windows.UI.Composition; @@ -26,6 +27,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations /// /// The SurfaceLoader is responsible to loading images into Composition Objects. /// + [Deprecated("This class is deprecated, please use the SurfaceLoader class from the Microsoft.Toolkit.Uwp.UI.Media package.", DeprecationType.Deprecate, 6)] public class SurfaceLoader { /// diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs new file mode 100644 index 00000000000..3ee477d605c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs @@ -0,0 +1,221 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A that implements an acrylic effect with customizable parameters + /// + public sealed class AcrylicBrush : XamlCompositionEffectBrushBase + { + /// + /// The instance in use to set the blur amount + /// + /// This is only set when is + private EffectSetter blurAmountSetter; + + /// + /// The instance in use to set the tint color + /// + private EffectSetter tintSetter; + + /// + /// The instance in use to set the tint mix amount + /// + private EffectSetter tintMixSetter; + + /// + /// Gets or sets the source mode for the effect + /// + public AcrylicBackgroundSource Source + { + get => (AcrylicBackgroundSource)GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( + nameof(Source), + typeof(AcrylicBackgroundSource), + typeof(AcrylicBrush), + new PropertyMetadata(AcrylicBackgroundSource.Backdrop, OnSourcePropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + /// Gets or sets the blur amount for the effect + /// + /// This property is ignored when the active mode is + public double BlurAmount + { + get => (double)GetValue(BlurAmountProperty); + set => SetValue(BlurAmountProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlurAmountProperty = DependencyProperty.Register( + nameof(BlurAmount), + typeof(double), + typeof(AcrylicBrush), + new PropertyMetadata(0.0, OnBlurAmountPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnBlurAmountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.Source != AcrylicBackgroundSource.HostBackdrop && // Blur is fixed by OS when using HostBackdrop source. + brush.CompositionBrush is CompositionBrush target) + { + brush.blurAmountSetter?.Invoke(target, (float)(double)e.NewValue); + } + } + + /// + /// Gets or sets the tint for the effect + /// + public Color Tint + { + get => (Color)GetValue(TintProperty); + set => SetValue(TintProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TintProperty = DependencyProperty.Register( + nameof(Tint), + typeof(Color), + typeof(AcrylicBrush), + new PropertyMetadata(default(Color), OnTintPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnTintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush is CompositionBrush target) + { + brush.tintSetter?.Invoke(target, (Color)e.NewValue); + } + } + + /// + /// Gets or sets the tint mix factor for the effect + /// + public double TintMix { get; set; } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TintMixProperty = DependencyProperty.Register( + nameof(TintMix), + typeof(double), + typeof(AcrylicBrush), + new PropertyMetadata(0.0, OnTintMixPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnTintMixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush is CompositionBrush target) + { + brush.tintMixSetter?.Invoke(target, (float)(double)e.NewValue); + } + } + + /// + /// Gets or sets the for the texture to use + /// + public Uri TextureUri + { + get => (Uri)GetValue(TextureUriProperty); + set => SetValue(TextureUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register( + nameof(TextureUri), + typeof(Uri), + typeof(AcrylicBrush), + new PropertyMetadata(default, OnTextureUriPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnTextureUriPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + protected override PipelineBuilder OnBrushRequested() + { + switch (this.Source) + { + case AcrylicBackgroundSource.Backdrop: + return PipelineBuilder.FromBackdropAcrylic( + Tint, + out tintSetter, + (float)TintMix, + out tintMixSetter, + (float)BlurAmount, + out this.blurAmountSetter, + TextureUri); + case AcrylicBackgroundSource.HostBackdrop: + return PipelineBuilder.FromHostBackdropAcrylic( + Tint, + out tintSetter, + (float)TintMix, + out tintMixSetter, + TextureUri); + default: throw new ArgumentOutOfRangeException(nameof(this.Source), $"Invalid acrylic source: {this.Source}"); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs index 0458960dd8d..69959a844c4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs @@ -4,7 +4,8 @@ //// Example brush from https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.xamlcompositionbrushbase -using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; @@ -14,85 +15,49 @@ namespace Microsoft.Toolkit.Uwp.UI.Media /// /// The is a that blurs whatever is behind it in the application. /// - public class BackdropBlurBrush : XamlCompositionBrushBase + public class BackdropBlurBrush : XamlCompositionEffectBrushBase { /// - /// Identifies the dependency property. + /// The instance currently in use /// - public static readonly DependencyProperty AmountProperty = DependencyProperty.Register( - nameof(Amount), - typeof(double), - typeof(BackdropBlurBrush), - new PropertyMetadata(0.0, new PropertyChangedCallback(OnAmountChanged))); + private EffectSetter setter; /// /// Gets or sets the amount of gaussian blur to apply to the background. /// public double Amount { - get { return (double)GetValue(AmountProperty); } - set { SetValue(AmountProperty, value); } - } - - private static void OnAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var brush = (BackdropBlurBrush)d; - - // Unbox and set a new blur amount if the CompositionBrush exists. - brush.CompositionBrush?.Properties.InsertScalar("Blur.BlurAmount", (float)(double)e.NewValue); + get => (double)GetValue(AmountProperty); + set => SetValue(AmountProperty, value); } /// - /// Initializes a new instance of the class. + /// Identifies the dependency property. /// - public BackdropBlurBrush() - { - } + public static readonly DependencyProperty AmountProperty = DependencyProperty.Register( + nameof(Amount), + typeof(double), + typeof(BackdropBlurBrush), + new PropertyMetadata(0.0, new PropertyChangedCallback(OnAmountChanged))); /// - /// Initializes the Composition Brush. + /// Updates the UI when changes /// - protected override void OnConnected() + /// The current instance + /// The instance for + private static void OnAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - // Delay creating composition resources until they're required. - if (CompositionBrush == null) + if (d is BackdropBlurBrush brush && + brush.CompositionBrush is CompositionBrush target) { - // Abort if effects aren't supported. - if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) - { - return; - } - - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); - - // Use a Win2D blur affect applied to a CompositionBackdropBrush. - var graphicsEffect = new GaussianBlurEffect - { - Name = "Blur", - BlurAmount = (float)Amount, - Source = new CompositionEffectSourceParameter("backdrop") - }; - - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[] { "Blur.BlurAmount" }); - var effectBrush = effectFactory.CreateBrush(); - - effectBrush.SetSourceParameter("backdrop", backdrop); - - CompositionBrush = effectBrush; + brush.setter?.Invoke(target, (float)brush.Amount); } } - /// - /// Deconstructs the Composition Brush. - /// - protected override void OnDisconnected() + /// + protected override PipelineBuilder OnBrushRequested() { - // Dispose of composition resources when no longer in use. - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - } + return PipelineBuilder.FromBackdrop().Blur((float)Amount, out setter); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropGammaTransferBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropGammaTransferBrush.cs index 17cf761988f..d17acb9bf28 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropGammaTransferBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropGammaTransferBrush.cs @@ -14,279 +14,318 @@ namespace Microsoft.Toolkit.Uwp.UI.Media /// public class BackdropGammaTransferBrush : XamlCompositionBrushBase { - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty AlphaAmplitudeProperty = - DependencyProperty.Register(nameof(AlphaAmplitude), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaAmplitude)))); - /// /// Gets or sets the amount of scale to apply to the alpha chennel. /// public double AlphaAmplitude { - get { return (double)GetValue(AlphaAmplitudeProperty); } - set { SetValue(AlphaAmplitudeProperty, value); } + get => (double)GetValue(AlphaAmplitudeProperty); + set => SetValue(AlphaAmplitudeProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty AlphaDisableProperty = - DependencyProperty.Register(nameof(AlphaDisable), typeof(bool), typeof(BackdropGammaTransferBrush), new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(AlphaDisable)))); + public static readonly DependencyProperty AlphaAmplitudeProperty = DependencyProperty.Register( + nameof(AlphaAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaAmplitude)))); /// /// Gets or sets a value indicating whether to disable alpha transfer. /// public bool AlphaDisable { - get { return (bool)GetValue(AlphaDisableProperty); } - set { SetValue(AlphaDisableProperty, value); } + get => (bool)GetValue(AlphaDisableProperty); + set => SetValue(AlphaDisableProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty AlphaExponentProperty = - DependencyProperty.Register(nameof(AlphaExponent), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaExponent)))); + public static readonly DependencyProperty AlphaDisableProperty = DependencyProperty.Register( + nameof(AlphaDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(AlphaDisable)))); /// /// Gets or sets the amount of scale to apply to the alpha chennel. /// public double AlphaExponent { - get { return (double)GetValue(AlphaExponentProperty); } - set { SetValue(AlphaExponentProperty, value); } + get => (double)GetValue(AlphaExponentProperty); + set => SetValue(AlphaExponentProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty AlphaOffsetProperty = - DependencyProperty.Register(nameof(AlphaOffset), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(AlphaOffset)))); + public static readonly DependencyProperty AlphaExponentProperty = DependencyProperty.Register( + nameof(AlphaExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaExponent)))); /// /// Gets or sets the amount of scale to apply to the alpha chennel. /// public double AlphaOffset { - get { return (double)GetValue(AlphaOffsetProperty); } - set { SetValue(AlphaOffsetProperty, value); } + get => (double)GetValue(AlphaOffsetProperty); + set => SetValue(AlphaOffsetProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty BlueAmplitudeProperty = - DependencyProperty.Register(nameof(BlueAmplitude), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueAmplitude)))); + public static readonly DependencyProperty AlphaOffsetProperty = DependencyProperty.Register( + nameof(AlphaOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(AlphaOffset)))); /// /// Gets or sets the amount of scale to apply to the Blue chennel. /// public double BlueAmplitude { - get { return (double)GetValue(BlueAmplitudeProperty); } - set { SetValue(BlueAmplitudeProperty, value); } + get => (double)GetValue(BlueAmplitudeProperty); + set => SetValue(BlueAmplitudeProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty BlueDisableProperty = - DependencyProperty.Register(nameof(BlueDisable), typeof(bool), typeof(BackdropGammaTransferBrush), new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(BlueDisable)))); + public static readonly DependencyProperty BlueAmplitudeProperty = DependencyProperty.Register( + nameof(BlueAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueAmplitude)))); /// /// Gets or sets a value indicating whether to disable Blue transfer. /// public bool BlueDisable { - get { return (bool)GetValue(BlueDisableProperty); } - set { SetValue(BlueDisableProperty, value); } + get => (bool)GetValue(BlueDisableProperty); + set => SetValue(BlueDisableProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty BlueExponentProperty = - DependencyProperty.Register(nameof(BlueExponent), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueExponent)))); + public static readonly DependencyProperty BlueDisableProperty = DependencyProperty.Register( + nameof(BlueDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(BlueDisable)))); /// /// Gets or sets the amount of scale to apply to the Blue chennel. /// public double BlueExponent { - get { return (double)GetValue(BlueExponentProperty); } - set { SetValue(BlueExponentProperty, value); } + get => (double)GetValue(BlueExponentProperty); + set => SetValue(BlueExponentProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty BlueOffsetProperty = - DependencyProperty.Register(nameof(BlueOffset), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(BlueOffset)))); + public static readonly DependencyProperty BlueExponentProperty = DependencyProperty.Register( + nameof(BlueExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueExponent)))); /// /// Gets or sets the amount of scale to apply to the Blue chennel. /// public double BlueOffset { - get { return (double)GetValue(BlueOffsetProperty); } - set { SetValue(BlueOffsetProperty, value); } + get => (double)GetValue(BlueOffsetProperty); + set => SetValue(BlueOffsetProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty GreenAmplitudeProperty = - DependencyProperty.Register(nameof(GreenAmplitude), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenAmplitude)))); + public static readonly DependencyProperty BlueOffsetProperty = DependencyProperty.Register( + nameof(BlueOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(BlueOffset)))); /// /// Gets or sets the amount of scale to apply to the Green chennel. /// public double GreenAmplitude { - get { return (double)GetValue(GreenAmplitudeProperty); } - set { SetValue(GreenAmplitudeProperty, value); } + get => (double)GetValue(GreenAmplitudeProperty); + set => SetValue(GreenAmplitudeProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty GreenDisableProperty = - DependencyProperty.Register(nameof(GreenDisable), typeof(bool), typeof(BackdropGammaTransferBrush), new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(GreenDisable)))); + public static readonly DependencyProperty GreenAmplitudeProperty = DependencyProperty.Register( + nameof(GreenAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenAmplitude)))); /// /// Gets or sets a value indicating whether to disable Green transfer. /// public bool GreenDisable { - get { return (bool)GetValue(GreenDisableProperty); } - set { SetValue(GreenDisableProperty, value); } + get => (bool)GetValue(GreenDisableProperty); + set => SetValue(GreenDisableProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty GreenExponentProperty = - DependencyProperty.Register(nameof(GreenExponent), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenExponent)))); + public static readonly DependencyProperty GreenDisableProperty = DependencyProperty.Register( + nameof(GreenDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(GreenDisable)))); /// /// Gets or sets the amount of scale to apply to the Green chennel. /// public double GreenExponent { - get { return (double)GetValue(GreenExponentProperty); } - set { SetValue(GreenExponentProperty, value); } + get => (double)GetValue(GreenExponentProperty); + set => SetValue(GreenExponentProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty GreenOffsetProperty = - DependencyProperty.Register(nameof(GreenOffset), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(GreenOffset)))); + public static readonly DependencyProperty GreenExponentProperty = DependencyProperty.Register( + nameof(GreenExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenExponent)))); /// /// Gets or sets the amount of scale to apply to the Green chennel. /// public double GreenOffset { - get { return (double)GetValue(GreenOffsetProperty); } - set { SetValue(GreenOffsetProperty, value); } + get => (double)GetValue(GreenOffsetProperty); + set => SetValue(GreenOffsetProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty RedAmplitudeProperty = - DependencyProperty.Register(nameof(RedAmplitude), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedAmplitude)))); + public static readonly DependencyProperty GreenOffsetProperty = DependencyProperty.Register( + nameof(GreenOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(GreenOffset)))); /// /// Gets or sets the amount of scale to apply to the Red chennel. /// public double RedAmplitude { - get { return (double)GetValue(RedAmplitudeProperty); } - set { SetValue(RedAmplitudeProperty, value); } + get => (double)GetValue(RedAmplitudeProperty); + set => SetValue(RedAmplitudeProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty RedDisableProperty = - DependencyProperty.Register(nameof(RedDisable), typeof(bool), typeof(BackdropGammaTransferBrush), new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(RedDisable)))); + public static readonly DependencyProperty RedAmplitudeProperty = DependencyProperty.Register( + nameof(RedAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedAmplitude)))); /// /// Gets or sets a value indicating whether to disable Red transfer. /// public bool RedDisable { - get { return (bool)GetValue(RedDisableProperty); } - set { SetValue(RedDisableProperty, value); } + get => (bool)GetValue(RedDisableProperty); + set => SetValue(RedDisableProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty RedExponentProperty = - DependencyProperty.Register(nameof(RedExponent), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedExponent)))); + public static readonly DependencyProperty RedDisableProperty = DependencyProperty.Register( + nameof(RedDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(RedDisable)))); /// /// Gets or sets the amount of scale to apply to the Red chennel. /// public double RedExponent { - get { return (double)GetValue(RedExponentProperty); } - set { SetValue(RedExponentProperty, value); } + get => (double)GetValue(RedExponentProperty); + set => SetValue(RedExponentProperty, value); } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty RedOffsetProperty = - DependencyProperty.Register(nameof(RedOffset), typeof(double), typeof(BackdropGammaTransferBrush), new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(RedOffset)))); + public static readonly DependencyProperty RedExponentProperty = DependencyProperty.Register( + nameof(RedExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedExponent)))); /// /// Gets or sets the amount of scale to apply to the Red chennel. /// public double RedOffset { - get { return (double)GetValue(RedOffsetProperty); } - set { SetValue(RedOffsetProperty, value); } + get => (double)GetValue(RedOffsetProperty); + set => SetValue(RedOffsetProperty, value); } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RedOffsetProperty = DependencyProperty.Register( + nameof(RedOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(RedOffset)))); + private static PropertyChangedCallback OnScalarPropertyChangedHelper(string propertyname) { - return new PropertyChangedCallback((d, e) => + return (d, e) => { var brush = (BackdropGammaTransferBrush)d; // Unbox and set a new blur amount if the CompositionBrush exists. brush.CompositionBrush?.Properties.InsertScalar("GammaTransfer." + propertyname, (float)(double)e.NewValue); - }); + }; } private static PropertyChangedCallback OnBooleanPropertyChangedHelper(string propertyname) { - return new PropertyChangedCallback((d, e) => + return (d, e) => { var brush = (BackdropGammaTransferBrush)d; // We can't animate our boolean properties so recreate our internal brush. brush.OnDisconnected(); brush.OnConnected(); - }); + }; } - /// - /// Initializes a new instance of the class. - /// - public BackdropGammaTransferBrush() - { - } - - /// - /// Initializes the Composition Brush. - /// + /// protected override void OnConnected() { // Delay creating composition resources until they're required. @@ -346,9 +385,7 @@ protected override void OnConnected() } } - /// - /// Deconstructs the Composition Brush. - /// + /// protected override void OnDisconnected() { // Dispose of composition resources when no longer in use. diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs index 09377b6b086..156a27bd7a7 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs @@ -4,9 +4,8 @@ //// Example brush from https://blogs.windows.com/buildingapps/2017/07/18/working-brushes-content-xaml-visual-layer-interop-part-one/#z70vPv1QMAvZsceo.97 -using Microsoft.Graphics.Canvas.Effects; -using Windows.UI.Composition; -using Windows.UI.Xaml; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI.Xaml.Media; namespace Microsoft.Toolkit.Uwp.UI.Media @@ -14,58 +13,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Media /// /// The is a which inverts whatever is behind it in the application. /// - public class BackdropInvertBrush : XamlCompositionBrushBase + public class BackdropInvertBrush : XamlCompositionEffectBrushBase { - /// - /// Initializes a new instance of the class. - /// - public BackdropInvertBrush() + /// + protected override PipelineBuilder OnBrushRequested() { - } - - /// - /// Initializes the Composition Brush. - /// - protected override void OnConnected() - { - // Delay creating composition resources until they're required. - if (CompositionBrush == null) - { - // Abort if effects aren't supported. - if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) - { - return; - } - - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); - - // Use a Win2D invert affect applied to a CompositionBackdropBrush. - var graphicsEffect = new InvertEffect - { - Name = "Invert", - Source = new CompositionEffectSourceParameter("backdrop") - }; - - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect); - var effectBrush = effectFactory.CreateBrush(); - - effectBrush.SetSourceParameter("backdrop", backdrop); - - CompositionBrush = effectBrush; - } - } - - /// - /// Deconstructs the Composition Brush. - /// - protected override void OnDisconnected() - { - // Dispose of composition resources when no longer in use. - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - } + return PipelineBuilder.FromBackdrop().Invert(); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs index f6f1355599c..f358cfeab77 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs @@ -2,18 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI.Composition; using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// Brush which applies a SaturationEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SaturationEffect.htm /// - public class BackdropSaturationBrush : XamlCompositionBrushBase + public class BackdropSaturationBrush : XamlCompositionEffectBrushBase { + /// + /// The instance currently in use + /// + private EffectSetter setter; + + /// + /// Gets or sets the amount of gaussian blur to apply to the background. + /// + public double Saturation + { + get => (double)GetValue(SaturationProperty); + set => SetValue(SaturationProperty, value); + } + /// /// Identifies the dependency property. /// @@ -24,14 +38,10 @@ public class BackdropSaturationBrush : XamlCompositionBrushBase new PropertyMetadata(0.5, new PropertyChangedCallback(OnSaturationChanged))); /// - /// Gets or sets the amount of gaussian blur to apply to the background. + /// Updates the UI when changes /// - public double Saturation - { - get { return (double)GetValue(SaturationProperty); } - set { SetValue(SaturationProperty, value); } - } - + /// The current instance + /// The instance for private static void OnSaturationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var brush = (BackdropSaturationBrush)d; @@ -47,61 +57,17 @@ private static void OnSaturationChanged(DependencyObject d, DependencyPropertyCh brush.Saturation = 0.0; } - // Unbox and set a new blur amount if the CompositionBrush exists. - brush.CompositionBrush?.Properties.InsertScalar("Saturation.Saturation", (float)brush.Saturation); - } - - /// - /// Initializes a new instance of the class. - /// - public BackdropSaturationBrush() - { - } - - /// - /// Initializes the Composition Brush. - /// - protected override void OnConnected() - { - // Delay creating composition resources until they're required. - if (CompositionBrush == null) + // Unbox and set a new blur amount if the CompositionBrush exists + if (brush.CompositionBrush is CompositionBrush target) { - // Abort if effects aren't supported. - if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) - { - return; - } - - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); - - // Use a Win2D blur affect applied to a CompositionBackdropBrush. - var graphicsEffect = new SaturationEffect - { - Name = "Saturation", - Saturation = (float)Saturation, - Source = new CompositionEffectSourceParameter("backdrop") - }; - - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[] { "Saturation.Saturation" }); - var effectBrush = effectFactory.CreateBrush(); - - effectBrush.SetSourceParameter("backdrop", backdrop); - - CompositionBrush = effectBrush; + brush.setter?.Invoke(target, (float)brush.Saturation); } } - /// - /// Deconstructs the Composition Brush. - /// - protected override void OnDisconnected() + /// + protected override PipelineBuilder OnBrushRequested() { - // Dispose of composition resources when no longer in use. - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - } + return PipelineBuilder.FromBackdrop().Saturation((float)Saturation, out setter); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs index 9606cfd73fa..487e6897fa1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs @@ -2,18 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI.Composition; using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// Brush which applies a SepiaEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SepiaEffect.htm /// - public class BackdropSepiaBrush : XamlCompositionBrushBase + public class BackdropSepiaBrush : XamlCompositionEffectBrushBase { + /// + /// The instance currently in use + /// + private EffectSetter setter; + + /// + /// Gets or sets the amount of gaussian blur to apply to the background. + /// + public double Intensity + { + get => (double)GetValue(IntensityProperty); + set => SetValue(IntensityProperty, value); + } + /// /// Identifies the dependency property. /// @@ -24,14 +38,10 @@ public class BackdropSepiaBrush : XamlCompositionBrushBase new PropertyMetadata(0.5, new PropertyChangedCallback(OnIntensityChanged))); /// - /// Gets or sets the amount of gaussian blur to apply to the background. + /// Updates the UI when changes /// - public double Intensity - { - get { return (double)GetValue(IntensityProperty); } - set { SetValue(IntensityProperty, value); } - } - + /// The current instance + /// The instance for private static void OnIntensityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var brush = (BackdropSepiaBrush)d; @@ -48,60 +58,16 @@ private static void OnIntensityChanged(DependencyObject d, DependencyPropertyCha } // Unbox and set a new blur amount if the CompositionBrush exists. - brush.CompositionBrush?.Properties.InsertScalar("Sepia.Intensity", (float)brush.Intensity); - } - - /// - /// Initializes a new instance of the class. - /// - public BackdropSepiaBrush() - { - } - - /// - /// Initializes the Composition Brush. - /// - protected override void OnConnected() - { - // Delay creating composition resources until they're required. - if (CompositionBrush == null) + if (brush.CompositionBrush is CompositionBrush target) { - // Abort if effects aren't supported. - if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) - { - return; - } - - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); - - // Use a Win2D blur affect applied to a CompositionBackdropBrush. - var graphicsEffect = new SepiaEffect - { - Name = "Sepia", - Intensity = (float)Intensity, - Source = new CompositionEffectSourceParameter("backdrop") - }; - - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[] { "Sepia.Intensity" }); - var effectBrush = effectFactory.CreateBrush(); - - effectBrush.SetSourceParameter("backdrop", backdrop); - - CompositionBrush = effectBrush; + brush.setter?.Invoke(target, (float)brush.Intensity); } } - /// - /// Deconstructs the Composition Brush. - /// - protected override void OnDisconnected() + /// + protected override PipelineBuilder OnBrushRequested() { - // Dispose of composition resources when no longer in use. - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - } + return PipelineBuilder.FromBackdrop().Sepia((float)Intensity, out setter); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs new file mode 100644 index 00000000000..fe075953d9d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Base +{ + /// + /// A custom that's ready to be used with a custom pipeline + /// + public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase + { + /// + /// The initialization instance + /// + private readonly AsyncMutex connectedMutex = new AsyncMutex(); + + /// + /// A method that builds and returns the pipeline to use in the current instance. + /// This method can also be used to store any needed or instances in local fields, for later use (they will need to be called upon ). + /// + /// A instance to create the brush to display + protected abstract PipelineBuilder OnBrushRequested(); + + private bool _isEnabled = true; + + /// + /// Gets or sets a value indicating whether the current brush is using the provided pipeline, or the fallback color + /// + public bool IsEnabled + { + get => this._isEnabled; + set => this.OnEnabledToggled(value); + } + + /// + protected override async void OnConnected() + { + using (await this.connectedMutex.LockAsync()) + { + if (this.CompositionBrush == null) + { + // Abort if effects aren't supported + if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) + { + return; + } + + if (this._isEnabled) + { + this.CompositionBrush = await this.OnBrushRequested().BuildAsync(); + } + else + { + this.CompositionBrush = await PipelineBuilder.FromColor(this.FallbackColor).BuildAsync(); + } + } + } + + base.OnConnected(); + } + + /// + protected override async void OnDisconnected() + { + using (await this.connectedMutex.LockAsync()) + { + if (this.CompositionBrush != null) + { + this.CompositionBrush.Dispose(); + this.CompositionBrush = null; + } + } + + base.OnDisconnected(); + } + + /// + /// Updates the property depending on the input value + /// + /// The new value being set to the property + protected async void OnEnabledToggled(bool value) + { + using (await this.connectedMutex.LockAsync()) + { + if (this._isEnabled == value) + { + return; + } + + this._isEnabled = value; + + if (this.CompositionBrush != null) + { + // Abort if effects aren't supported + if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) + { + return; + } + + if (this._isEnabled) + { + this.CompositionBrush = await this.OnBrushRequested().BuildAsync(); + } + else + { + this.CompositionBrush = await PipelineBuilder.FromColor(this.FallbackColor).BuildAsync(); + } + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs index 41c746320e7..4bfa1dcd9d7 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs @@ -6,7 +6,6 @@ using System; using Microsoft.Graphics.Canvas.Effects; -using Windows.UI; using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; @@ -22,6 +21,15 @@ public class ImageBlendBrush : XamlCompositionBrushBase private LoadedImageSurface _surface; private CompositionSurfaceBrush _surfaceBrush; + /// + /// Gets or sets the source of the image to composite. + /// + public ImageSource Source + { + get => (ImageSource)GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + /// /// Identifies the dependency property. /// @@ -29,15 +37,15 @@ public class ImageBlendBrush : XamlCompositionBrushBase nameof(Source), typeof(ImageSource), // We use ImageSource type so XAML engine will automatically construct proper object from String. typeof(ImageBlendBrush), - new PropertyMetadata(null, new PropertyChangedCallback(OnImageSourceChanged))); + new PropertyMetadata(null, OnImageSourceChanged)); /// - /// Gets or sets the source of the image to composite. + /// Gets or sets how to stretch the image within the brush. /// - public ImageSource Source + public Stretch Stretch { - get { return (ImageSource)GetValue(SourceProperty); } - set { SetValue(SourceProperty, value); } + get => (Stretch)GetValue(StretchProperty); + set => SetValue(StretchProperty, value); } /// @@ -48,15 +56,15 @@ public ImageSource Source nameof(Stretch), typeof(Stretch), typeof(ImageBlendBrush), - new PropertyMetadata(Stretch.None, new PropertyChangedCallback(OnStretchChanged))); + new PropertyMetadata(Stretch.None, OnStretchChanged)); /// - /// Gets or sets how to stretch the image within the brush. + /// Gets or sets how to blend the image with the backdrop. /// - public Stretch Stretch + public ImageBlendMode Mode { - get { return (Stretch)GetValue(StretchProperty); } - set { SetValue(StretchProperty, value); } + get => (ImageBlendMode)GetValue(ModeProperty); + set => SetValue(ModeProperty, value); } /// @@ -66,16 +74,7 @@ public Stretch Stretch nameof(Mode), typeof(ImageBlendMode), typeof(ImageBlendBrush), - new PropertyMetadata(ImageBlendMode.Multiply, new PropertyChangedCallback(OnModeChanged))); - - /// - /// Gets or sets how to blend the image with the backdrop. - /// - public ImageBlendMode Mode - { - get { return (ImageBlendMode)GetValue(ModeProperty); } - set { SetValue(ModeProperty, value); } - } + new PropertyMetadata(ImageBlendMode.Multiply, OnModeChanged)); private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -120,16 +119,7 @@ private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedE brush.OnConnected(); } - /// - /// Initializes a new instance of the class. - /// - public ImageBlendBrush() - { - } - - /// - /// Initializes the Composition Brush. - /// + /// protected override void OnConnected() { // Delay creating composition resources until they're required. @@ -172,9 +162,7 @@ protected override void OnConnected() } } - /// - /// Deconstructs the Composition Brush. - /// + /// protected override void OnDisconnected() { // Dispose of composition resources when no longer in use. diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendMode.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendMode.cs deleted file mode 100644 index 0d93f1f529a..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendMode.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -//// Composition supported version of http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm. - -namespace Microsoft.Toolkit.Uwp.UI.Media -{ - /// - /// Blend mode to use when compositing effects. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm for details. - /// Dissolve is not supported. - /// - public enum ImageBlendMode - { - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - see http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm. - Multiply = 0, - Screen = 1, - Darken = 2, - Lighten = 3, - ////Dissolve = 4, // Not Supported - ColorBurn = 5, - LinearBurn = 6, - DarkerColor = 7, - LighterColor = 8, - ColorDodge = 9, - LinearDodge = 10, - Overlay = 11, - SoftLight = 12, - HardLight = 13, - VividLight = 14, - LinearLight = 15, - PinLight = 16, - HardMix = 17, - Difference = 18, - Exclusion = 19, - - /// - /// Hue blend mode. Requires 16299 or higher. - /// - Hue = 20, - - /// - /// Saturation blend mode. Requires 16299 or higher. - /// - Saturation = 21, - - /// - /// Color blend mode. Requires 16299 or higher. - /// - Color = 22, - - /// - /// Luminosity blend mode. Requires 16299 or higher. - /// - Luminosity = 23, - Subtract = 24, - Division = 25, - #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs new file mode 100644 index 00000000000..d9a0b6d8846 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Xaml.Media; +using BlendEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.BlendEffect; +using ExposureEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.ExposureEffect; +using GrayscaleEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.GrayscaleEffect; +using HueRotationEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.HueRotationEffect; +using InvertEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.InvertEffect; +using LuminanceToAlphaEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.LuminanceToAlphaEffect; +using OpacityEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.OpacityEffect; +using SaturationEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.SaturationEffect; +using SepiaEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.SepiaEffect; +using TemperatureAndTintEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.TemperatureAndTintEffect; +using TileEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.TileEffect; +using TintEffect = Microsoft.Toolkit.Uwp.UI.Media.Effects.TintEffect; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A that renders a customizable Composition/Win2D effects pipeline + /// + public sealed class PipelineBrush : XamlCompositionEffectBrushBase + { + /// + /// Builds a new effects pipeline from the input effects sequence + /// + /// The input collection of instance + /// A new instance with the items in + [Pure] + private static PipelineBuilder Build(IList effects) + { + if (effects.Count == 0) + { + throw new ArgumentException("An effects pipeline can't be empty"); + } + + return effects.Skip(1).Aggregate(Start(effects[0]), (b, e) => Append(e, b)); + } + + /// + /// Starts a new composition pipeline from the given effect + /// + /// The initial instance + /// A new instance starting from + [Pure] + private static PipelineBuilder Start(IPipelineEffect effect) + { + switch (effect) + { + case BackdropEffect backdrop when backdrop.Source == AcrylicBackgroundSource.Backdrop: + return PipelineBuilder.FromBackdrop(); + case BackdropEffect backdrop when backdrop.Source == AcrylicBackgroundSource.HostBackdrop: + return PipelineBuilder.FromHostBackdrop(); + case SolidColorEffect color: + return PipelineBuilder.FromColor(color.Color); + case ImageEffect image: + return PipelineBuilder.FromImage(image.Uri, image.DpiMode, image.CacheMode); + case TileEffect tile: + return PipelineBuilder.FromTiles(tile.Uri, tile.DpiMode, tile.CacheMode); + case AcrylicEffect acrylic when acrylic.Source == AcrylicBackgroundSource.Backdrop: + return PipelineBuilder.FromBackdropAcrylic(acrylic.Tint, (float)acrylic.TintMix, (float)acrylic.BlurAmount, acrylic.TextureUri); + case AcrylicEffect acrylic when acrylic.Source == AcrylicBackgroundSource.HostBackdrop: + return PipelineBuilder.FromHostBackdropAcrylic(acrylic.Tint, (float)acrylic.TintMix, acrylic.TextureUri); + default: + throw new ArgumentException($"Invalid initial pipeline effect: {effect.GetType()}"); + } + } + + /// + /// Appends an effect to an existing composition pipeline + /// + /// The instance to append to the current pipeline + /// The target instance to modify + /// The target instance in use + private static PipelineBuilder Append(IPipelineEffect effect, PipelineBuilder builder) + { + switch (effect) + { + case OpacityEffect opacity: + return builder.Opacity((float)opacity.Value); + case LuminanceToAlphaEffect _: + return builder.LuminanceToAlpha(); + case InvertEffect _: + return builder.Invert(); + case GrayscaleEffect _: + return builder.Grayscale(); + case ExposureEffect exposure: + return builder.Exposure((float)exposure.Value); + case SepiaEffect sepia: + return builder.Sepia((float)sepia.Value); + case ShadeEffect shade: + return builder.Shade(shade.Color, (float)shade.Intensity); + case HueRotationEffect hueRotation: + return builder.HueRotation((float)hueRotation.Angle); + case TintEffect tint: + return builder.Tint(tint.Color); + case TemperatureAndTintEffect temperatureAndTint: + return builder.TemperatureAndTint((float)temperatureAndTint.Temperature, (float)temperatureAndTint.Tint); + case BlurEffect blur: + return builder.Blur((float)blur.Value); + case SaturationEffect saturation: + return builder.Saturation((float)saturation.Value); + case BlendEffect blend: + return builder.Blend(Build(blend.Input), (BlendEffectMode)blend.Mode, blend.Placement); + default: + throw new ArgumentException($"Invalid pipeline effect: {effect.GetType()}"); + } + } + + /// + protected override PipelineBuilder OnBrushRequested() + { + return Build(this.Effects); + } + + /// + /// Gets or sets the collection of effects to use in the current pipeline + /// + public IList Effects { get; set; } = new List(); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.Properties.cs index f0d5863786b..26e3ee07630 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.Properties.cs @@ -18,8 +18,8 @@ public partial class RadialGradientBrush /// public AlphaMode AlphaMode { - get { return (AlphaMode)GetValue(AlphaModeProperty); } - set { SetValue(AlphaModeProperty, value); } + get => (AlphaMode)GetValue(AlphaModeProperty); + set => SetValue(AlphaModeProperty, value); } /// @@ -33,8 +33,8 @@ public AlphaMode AlphaMode /// public ColorInterpolationMode ColorInterpolationMode { - get { return (ColorInterpolationMode)GetValue(ColorInterpolationModeProperty); } - set { SetValue(ColorInterpolationModeProperty, value); } + get => (ColorInterpolationMode)GetValue(ColorInterpolationModeProperty); + set => SetValue(ColorInterpolationModeProperty, value); } /// @@ -48,8 +48,8 @@ public ColorInterpolationMode ColorInterpolationMode /// public GradientStopCollection GradientStops { - get { return (GradientStopCollection)GetValue(GradientStopsProperty); } - set { SetValue(GradientStopsProperty, value); } + get => (GradientStopCollection)GetValue(GradientStopsProperty); + set => SetValue(GradientStopsProperty, value); } /// @@ -63,8 +63,8 @@ public GradientStopCollection GradientStops /// public Point Center { - get { return (Point)GetValue(CenterProperty); } - set { SetValue(CenterProperty, value); } + get => (Point)GetValue(CenterProperty); + set => SetValue(CenterProperty, value); } /// @@ -78,8 +78,8 @@ public Point Center /// public Point GradientOrigin { - get { return (Point)GetValue(GradientOriginProperty); } - set { SetValue(GradientOriginProperty, value); } + get => (Point)GetValue(GradientOriginProperty); + set => SetValue(GradientOriginProperty, value); } /// @@ -93,8 +93,8 @@ public Point GradientOrigin /// public double RadiusX { - get { return (double)GetValue(RadiusXProperty); } - set { SetValue(RadiusXProperty, value); } + get => (double)GetValue(RadiusXProperty); + set => SetValue(RadiusXProperty, value); } /// @@ -108,8 +108,8 @@ public double RadiusX /// public double RadiusY { - get { return (double)GetValue(RadiusYProperty); } - set { SetValue(RadiusYProperty, value); } + get => (double)GetValue(RadiusYProperty); + set => SetValue(RadiusYProperty, value); } /// @@ -123,8 +123,8 @@ public double RadiusY /// public GradientSpreadMethod SpreadMethod { - get { return (GradientSpreadMethod)GetValue(SpreadMethodProperty); } - set { SetValue(SpreadMethodProperty, value); } + get => (GradientSpreadMethod)GetValue(SpreadMethodProperty); + set => SetValue(SpreadMethodProperty, value); } /// diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrushInterop.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrushInterop.cs index 8a6b23047e2..15910f486a3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrushInterop.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrushInterop.cs @@ -89,7 +89,7 @@ public static CanvasGradientStop[] ToWin2DGradientStops(this GradientStopCollect int x = 0; foreach (var stop in stops) { - canvasStops[x++] = new CanvasGradientStop() { Color = stop.Color, Position = (float)stop.Offset }; + canvasStops[x++] = new CanvasGradientStop { Color = stop.Color, Position = (float)stop.Offset }; } return canvasStops; diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs new file mode 100644 index 00000000000..be1950c9bd3 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A that displays a tiled image + /// + public sealed class TilesBrush : XamlCompositionEffectBrushBase + { + /// + /// Gets or sets the to the texture to use + /// + public Uri TextureUri + { + get => (Uri)GetValue(TextureUriProperty); + set => SetValue(TextureUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register( + nameof(TextureUri), + typeof(Uri), + typeof(TilesBrush), + new PropertyMetadata(default, OnDependencyPropertyChanged)); + + /// + /// Gets or sets the DPI mode used to render the texture (the default is ) + /// + public DpiMode DpiMode + { + get => (DpiMode)GetValue(DpiModeProperty); + set => SetValue(DpiModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DpiModeProperty = DependencyProperty.Register( + nameof(DpiMode), + typeof(DpiMode), + typeof(TilesBrush), + new PropertyMetadata(DpiMode.DisplayDpiWith96AsLowerBound, OnDependencyPropertyChanged)); + + /// + /// Updates the UI when either or changes + /// + /// The current instance + /// The instance for or + private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TilesBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + protected override PipelineBuilder OnBrushRequested() + { + if (TextureUri is Uri uri) + { + return PipelineBuilder.FromTiles(uri, DpiMode); + } + + return PipelineBuilder.FromColor(default); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs new file mode 100644 index 00000000000..7a2ccaa3e0a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.UI.Media.Base; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A that represents a custom effect setter that can be applied to a instance + /// + /// The type of property value to set + /// The effect target value + public delegate void XamlEffectSetter(T value) + where T : unmanaged; + + /// + /// A that represents a custom effect animation that can be applied to a instance + /// + /// The type of property value to animate + /// The animation target value + /// The animation duration + /// A that completes when the target animation completes + public delegate Task XamlEffectAnimation(T value, TimeSpan duration) + where T : unmanaged; + + /// + /// A simple that can be used to quickly create XAML brushes from arbitrary pipelines + /// + public sealed class XamlCompositionBrush : XamlCompositionEffectBrushBase + { + /// + /// Gets the pipeline for the current instance + /// + public PipelineBuilder Pipeline { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to create the effect + public XamlCompositionBrush(PipelineBuilder pipeline) => this.Pipeline = pipeline; + + /// + /// Binds an to the composition brush in the current instance + /// + /// The type of property value to set + /// The input setter + /// The resulting setter + /// The current instance + [Pure] + public XamlCompositionBrush Bind(EffectSetter setter, out XamlEffectSetter bound) + where T : unmanaged + { + bound = value => setter(this.CompositionBrush, value); + + return this; + } + + /// + /// Binds an to the composition brush in the current instance + /// + /// The type of property value to animate + /// The input animation + /// The resulting animation + /// The current instance + [Pure] + public XamlCompositionBrush Bind(EffectAnimation animation, out XamlEffectAnimation bound) + where T : unmanaged + { + bound = (value, duration) => animation(this.CompositionBrush, value, duration); + + return this; + } + + /// + protected override PipelineBuilder OnBrushRequested() => this.Pipeline; + + /// + /// Clones the current instance by rebuilding the source . Use this method to reuse the same effects pipeline on a different + /// + /// A instance using the current effects pipeline + [Pure] + public XamlCompositionBrush Clone() + { + if (this.Dispatcher.HasThreadAccess) + { + throw new InvalidOperationException("The current thread already has access to the brush dispatcher, so a clone operation is not necessary. " + + "You can just assign this brush to an arbitrary number of controls and it will still work correctly. " + + "This method is only meant to be used to create a new instance of this brush using the same pipeline, " + + "on threads that can't access the current instance, for example in secondary app windows."); + } + + return new XamlCompositionBrush(this.Pipeline); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageEffectBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageEffectBase.cs new file mode 100644 index 00000000000..2934d4e23a5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageEffectBase.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract +{ + /// + /// An image based effect that loads an image at the specified location + /// + public abstract class ImageEffectBase : IPipelineEffect + { + /// + /// Gets or sets the for the image to load + /// + public Uri Uri { get; set; } + + /// + /// Gets or sets the DPI mode used to render the image (the default is ) + /// + public DpiMode DpiMode { get; set; } = DpiMode.DisplayDpiWith96AsLowerBound; + + /// + /// Gets or sets the cache mode to use when loading the image (the default is ) + /// + public CacheMode CacheMode { get; set; } = CacheMode.Default; + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ValueEffectBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ValueEffectBase.cs new file mode 100644 index 00000000000..5e912a8c86b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ValueEffectBase.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract +{ + /// + /// A base for an effect that exposes a single parameter + /// + public abstract class ValueEffectBase : IPipelineEffect + { + /// + /// Gets or sets the value of the parameter for the current effect + /// + public double Value { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/AcrylicEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/AcrylicEffect.cs new file mode 100644 index 00000000000..13c6208518d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/AcrylicEffect.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Windows.UI; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A custom acrylic effect that can be inserted into a pipeline + /// + /// This effect mirrors the look of the default implementation + public sealed class AcrylicEffect : IPipelineEffect + { + /// + /// Gets or sets the source mode for the effect + /// + public AcrylicBackgroundSource Source { get; set; } + + /// + /// Gets or sets the blur amount for the effect + /// + /// This property is ignored when the active mode is + public double BlurAmount { get; set; } + + /// + /// Gets or sets the tint for the effect + /// + public Color Tint { get; set; } + + /// + /// Gets or sets the color for the tint effect + /// + public double TintMix { get; set; } + + /// + /// Gets or sets the to the texture to use + /// + public Uri TextureUri { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BackdropEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BackdropEffect.cs new file mode 100644 index 00000000000..5b20e450dcb --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BackdropEffect.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A backdrop effect that can sample from a specified source + /// + public sealed class BackdropEffect : IPipelineEffect + { + /// + /// Gets or sets the backdrop source to use to render the effect + /// + public AcrylicBackgroundSource Source { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs new file mode 100644 index 00000000000..611aae4a5e5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A blend effect that merges the current pipeline with an input one + /// + /// This effect maps to the Win2D effect + public sealed class BlendEffect : IPipelineEffect + { + /// + /// Gets or sets the input pipeline to merge with the current instance + /// + public IList Input { get; set; } = new List(); + + /// + /// Gets or sets the blending mode to use (the default mode is ) + /// + public ImageBlendMode Mode { get; set; } + + /// + /// Gets or sets the placement of the input pipeline with respect to the current one (the default is ) + /// + public Placement Placement { get; set; } = Placement.Foreground; + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs new file mode 100644 index 00000000000..3164f245e88 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A gaussian blur effect + /// + /// This effect maps to the Win2D effect + public sealed class BlurEffect : ValueEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs new file mode 100644 index 00000000000..e038c63cd4c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An exposure effect + /// + /// This effect maps to the Win2D effect + public sealed class ExposureEffect : ValueEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs new file mode 100644 index 00000000000..2b1a246b6e5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A grayscale effect + /// + /// This effect maps to the Win2D effect + public sealed class GrayscaleEffect : IPipelineEffect + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs new file mode 100644 index 00000000000..ed90a0b87b8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A hue rotation effect + /// + /// This effect maps to the Win2D effect + public sealed class HueRotationEffect : IPipelineEffect + { + /// + /// Gets or sets the angle to rotate the hue, in radians + /// + public double Angle { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ImageEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ImageEffect.cs new file mode 100644 index 00000000000..d95dc44b93d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ImageEffect.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An image effect, which displays an image loaded as a Win2D surface + /// + public sealed class ImageEffect : ImageEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs new file mode 100644 index 00000000000..015355a22b8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces +{ + /// + /// The base for all the pipeline effects to be used in a + /// + public interface IPipelineEffect + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs new file mode 100644 index 00000000000..b5f5565c79f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An color inversion effect + /// + /// This effect maps to the Win2D effect + public sealed class InvertEffect : IPipelineEffect + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs new file mode 100644 index 00000000000..82f3547e816 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A luminance to alpha effect + /// + /// This effect maps to the Win2D effect + public sealed class LuminanceToAlphaEffect : IPipelineEffect + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs new file mode 100644 index 00000000000..7b86916f90d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An opacity effect + /// + /// This effect maps to the Win2D effect + public sealed class OpacityEffect : ValueEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs new file mode 100644 index 00000000000..9b30e0a4041 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A saturation effect + /// + /// This effect maps to the Win2D effect + public sealed class SaturationEffect : ValueEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs new file mode 100644 index 00000000000..baf9e816d25 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A sepia effect + /// + /// This effect maps to the Win2D effect + public sealed class SepiaEffect : ValueEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs new file mode 100644 index 00000000000..b2659b27719 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An effect that overlays a color layer over the current pipeline, with a specified intensity + /// + public sealed class ShadeEffect : IPipelineEffect + { + /// + /// Gets or sets the color to use + /// + public Color Color { get; set; } + + /// + /// Gets or sets the intensity of the color layer + /// + public double Intensity { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SolidColorEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SolidColorEffect.cs new file mode 100644 index 00000000000..b0f90ae2989 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SolidColorEffect.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An effect that renders a standard 8bit SDR color on the available surface + /// + public sealed class SolidColorEffect : IPipelineEffect + { + /// + /// Gets or sets the color to display + /// + public Color Color { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs new file mode 100644 index 00000000000..058e1019e70 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A temperature and tint effect + /// + /// This effect maps to the Win2D effect + public sealed class TemperatureAndTintEffect : ValueEffectBase + { + /// + /// Gets or sets the value of the temperature for the current effect + /// + public double Temperature { get; set; } + + /// + /// Gets or sets the value of the tint for the current effect + /// + public double Tint { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TileEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TileEffect.cs new file mode 100644 index 00000000000..b2a340b5dc2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TileEffect.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Abstract; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// An effect that loads an image and replicates it to cover all the available surface area + /// + /// This effect maps to the Win2D effect + public sealed class TileEffect : ImageEffectBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs new file mode 100644 index 00000000000..bc248f82890 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Effects.Interfaces; +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +{ + /// + /// A tint effect + /// + /// This effect maps to the Win2D effect + public sealed class TintEffect : IPipelineEffect + { + /// + /// Gets or sets the int color to use + /// + public Color Color { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AlphaMode.cs b/Microsoft.Toolkit.Uwp.UI.Media/Enums/AlphaMode.cs similarity index 85% rename from Microsoft.Toolkit.Uwp.UI.Media/Brushes/AlphaMode.cs rename to Microsoft.Toolkit.Uwp.UI.Media/Enums/AlphaMode.cs index 8d248160f3b..2b28ead6fe5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AlphaMode.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Enums/AlphaMode.cs @@ -2,12 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Toolkit.Uwp.UI.Media { /// diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Enums/CacheMode.cs b/Microsoft.Toolkit.Uwp.UI.Media/Enums/CacheMode.cs new file mode 100644 index 00000000000..a1c3818948d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Enums/CacheMode.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// Indicates the cache mode to use when loading a Win2D image + /// + public enum CacheMode + { + /// + /// The default behavior, the cache is enabled + /// + Default, + + /// + /// Reload the target image and overwrite the cached entry, if it exists + /// + Overwrite, + + /// + /// The cache is disabled and new images are always reloaded + /// + Disabled + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Enums/DpiMode.cs b/Microsoft.Toolkit.Uwp.UI.Media/Enums/DpiMode.cs new file mode 100644 index 00000000000..049dd29487b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Enums/DpiMode.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// Indicates the DPI mode to use to load an image + /// + public enum DpiMode + { + /// + /// Uses the original DPI settings of the loaded image + /// + UseSourceDpi, + + /// + /// Uses the default value of 96 DPI + /// + Default96Dpi, + + /// + /// Overrides the image DPI settings with the current screen DPI value + /// + DisplayDpi, + + /// + /// Overrides the image DPI settings with the current screen DPI value and ensures the resulting value is at least 96 + /// + DisplayDpiWith96AsLowerBound + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Enums/ImageBlendMode.cs b/Microsoft.Toolkit.Uwp.UI.Media/Enums/ImageBlendMode.cs new file mode 100644 index 00000000000..6a95de35d6c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Enums/ImageBlendMode.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//// Composition supported version of http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm. + +using Microsoft.Graphics.Canvas.Effects; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - see http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm. + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// Blend mode to use when compositing effects. + /// See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm for details. + /// Dissolve is not supported. + /// + public enum ImageBlendMode + { + Multiply = BlendEffectMode.Multiply, + Screen = BlendEffectMode.Screen, + Darken = BlendEffectMode.Darken, + Lighten = BlendEffectMode.Lighten, + ColorBurn = BlendEffectMode.ColorBurn, + LinearBurn = BlendEffectMode.LinearBurn, + DarkerColor = BlendEffectMode.DarkerColor, + LighterColor = BlendEffectMode.LighterColor, + ColorDodge = BlendEffectMode.ColorDodge, + LinearDodge = BlendEffectMode.LinearDodge, + Overlay = BlendEffectMode.Overlay, + SoftLight = BlendEffectMode.SoftLight, + HardLight = BlendEffectMode.HardLight, + VividLight = BlendEffectMode.VividLight, + LinearLight = BlendEffectMode.LinearLight, + PinLight = BlendEffectMode.PinLight, + HardMix = BlendEffectMode.HardMix, + Difference = BlendEffectMode.Difference, + Exclusion = BlendEffectMode.Exclusion, + + /// + /// Hue blend mode. + /// + Hue = BlendEffectMode.Hue, + + /// + /// Saturation blend mode. + /// + Saturation = BlendEffectMode.Saturation, + + /// + /// Color blend mode. + /// + Color = BlendEffectMode.Color, + + /// + /// Luminosity blend mode. + /// + Luminosity = BlendEffectMode.Luminosity, + Subtract = BlendEffectMode.Subtract, + Division = BlendEffectMode.Division, + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Enums/Placement.cs b/Microsoft.Toolkit.Uwp.UI.Media/Enums/Placement.cs new file mode 100644 index 00000000000..055632c6294 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Enums/Placement.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Graphics.Effects; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// An used to modify the default placement of the input instance in a blend operation + /// + public enum Placement + { + /// + /// The instance used to call the blend method is placed on top of the other + /// + Foreground, + + /// + /// The instance used to call the blend method is placed behind the other + /// + Background + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Collections.Generic/GenericExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Collections.Generic/GenericExtensions.cs new file mode 100644 index 00000000000..202669d3da4 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Collections.Generic/GenericExtensions.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Extensions +{ + /// + /// An extension for the + /// + internal static class GenericExtensions + { + /// + /// Merges the two input instances and makes sure no duplicate keys are present + /// + /// The type of keys in the input dictionaries + /// The type of values in the input dictionaries + /// The first to merge + /// The second to merge + /// An instance with elements from both and + [Pure] + public static IReadOnlyDictionary Merge( + this IReadOnlyDictionary a, + IReadOnlyDictionary b) + { + if (a.Keys.FirstOrDefault(b.ContainsKey) is TKey key) + { + throw new InvalidOperationException($"The key {key} already exists in the current pipeline"); + } + + return new Dictionary(a.Concat(b)); + } + + /// + /// Merges the two input instances and makes sure no duplicate items are present + /// + /// The type of elements in the input collections + /// The first to merge + /// The second to merge + /// An instance with elements from both and + [Pure] + public static IReadOnlyCollection Merge(this IReadOnlyCollection a, IReadOnlyCollection b) + { + if (a.Any(b.Contains)) + { + throw new InvalidOperationException("The input collection has at least an item already present in the second collection"); + } + + return a.Concat(b).ToArray(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs new file mode 100644 index 00000000000..d820f502f83 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Extensions +{ + /// + /// An implementation that can be easily used inside a block + /// + internal sealed class AsyncMutex + { + /// + /// The underlying instance in use + /// + private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); + + /// + /// Acquires a lock for the current instance, that is automatically released outside the block + /// + /// A that returns an instance to release the lock + public async Task LockAsync() + { + await this.semaphore.WaitAsync().ConfigureAwait(false); + + return new Lock(this.semaphore); + } + + /// + /// Private class that implements the automatic release of the semaphore + /// + private sealed class Lock : IDisposable + { + /// + /// The instance of the parent class + /// + private readonly SemaphoreSlim semaphore; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of the parent class + public Lock(SemaphoreSlim semaphore) + { + this.semaphore = semaphore; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void IDisposable.Dispose() + { + this.semaphore.Release(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/GuidExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/GuidExtensions.cs new file mode 100644 index 00000000000..dcc7435f3b7 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/GuidExtensions.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Extensions +{ + /// + /// An extension for the type + /// + public static class GuidExtensions + { + /// + /// Returns a representation of a only made of uppercase letters + /// + /// The input to process + /// A representation of only made up of letters in the [A-Z] range + [Pure] + internal static string ToUppercaseAsciiLetters(this Guid guid) + { + return new string(( + from c in guid.ToString("N") + let l = char.IsDigit(c) ? (char)('G' + c - '0') : c + select l).ToArray()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/UriExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/UriExtensions.cs new file mode 100644 index 00000000000..5395a03337c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/System/UriExtensions.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Extensions +{ + /// + /// An extension for the type + /// + public static class UriExtensions + { + /// + /// Returns an that starts with the ms-appx:// prefix + /// + /// The input to process + /// A equivalent to the first but relative to ms-appx:// + /// This is needed because the XAML converter doesn't use the ms-appx:// prefix + [Pure] + internal static Uri ToAppxUri(this Uri uri) + { + if (uri.Scheme.Equals("ms-resource")) + { + string path = uri.AbsolutePath.StartsWith("/Files") + ? uri.AbsolutePath.Replace("/Files", string.Empty) + : uri.AbsolutePath; + + return new Uri($"ms-appx://{path}"); + } + + return uri; + } + + /// + /// Returns an that starts with the ms-appx:// prefix + /// + /// The input relative path to convert + /// A with relative to ms-appx:// + [Pure] + public static Uri ToAppxUri(this string path) + { + string prefix = $"ms-appx://{(path.StartsWith('/') ? string.Empty : "/")}"; + + return new Uri($"{prefix}{path}"); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs new file mode 100644 index 00000000000..e87227b4e6c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Numerics; +using System.Threading.Tasks; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Extensions +{ + /// + /// An extension for the type + /// + internal static class CompositionObjectExtensions + { + /// + /// Starts an to keep the size of the source in sync with the target + /// + /// The to start the animation on + /// The target to read the size updates from + public static void BindSize(this CompositionObject source, UIElement target) + { + var visual = ElementCompositionPreview.GetElementVisual(target); + var bindSizeAnimation = source.Compositor.CreateExpressionAnimation($"{nameof(visual)}.Size"); + + bindSizeAnimation.SetReferenceParameter(nameof(visual), visual); + + // Start the animation + source.StartAnimation("Size", bindSizeAnimation); + } + + /// + /// Starts an animation on the given property of a + /// + /// The type of the property to animate + /// The target + /// The name of the property to animate + /// The final value of the property + /// The animation duration + /// A that completes when the created animation completes + public static Task StartAnimationAsync(this CompositionObject target, string property, T value, TimeSpan duration) + where T : unmanaged + { + // Stop previous animations + target.StopAnimation(property); + + // Setup the animation to run + KeyFrameAnimation animation; + switch (value) + { + case float f: + var scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation(); + scalarAnimation.InsertKeyFrame(1f, f); + animation = scalarAnimation; + break; + case Color c: + var colorAnimation = target.Compositor.CreateColorKeyFrameAnimation(); + colorAnimation.InsertKeyFrame(1f, c); + animation = colorAnimation; + break; + case Vector4 v4: + var vector4Animation = target.Compositor.CreateVector4KeyFrameAnimation(); + vector4Animation.InsertKeyFrame(1f, v4); + animation = vector4Animation; + break; + default: throw new ArgumentException($"Invalid animation type: {typeof(T)}", nameof(value)); + } + + animation.Duration = duration; + + // Get the batch and start the animations + var batch = target.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); + + var tcs = new TaskCompletionSource(); + + batch.Completed += (s, e) => tcs.SetResult(null); + + target.StartAnimation(property, animation); + + batch.End(); + + return tcs.Task; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs new file mode 100644 index 00000000000..01bdb72e9ce --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Helpers.Cache +{ + /// + /// A used to cache reusable instances with an associated key + /// + /// The type of key to classify the items in the cache + /// The type of items stored in the cache + internal sealed class CompositionObjectCache + where TValue : CompositionObject + { + /// + /// The cache of weak references of type to instances, to avoid memory leaks + /// + private readonly ConditionalWeakTable>> cache = new ConditionalWeakTable>>(); + + /// + /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found + /// + /// The current instance to get the value for + /// The key to look for + /// The resulting value, if existing + /// if the target value has been found, otherwise + public bool TryGetValue(Compositor compositor, TKey key, out TValue result) + { + lock (this.cache) + { + if (this.cache.TryGetValue(compositor, out var map) && + map.TryGetValue(key, out var reference) && + reference.TryGetTarget(out result)) + { + return true; + } + + result = null; + return false; + } + } + + /// + /// Adds or updates a value with the specified key to the cache + /// + /// The current instance to get the value for + /// The key of the item to add + /// The value to add + public void AddOrUpdate(Compositor compositor, TKey key, TValue value) + { + lock (this.cache) + { + if (this.cache.TryGetValue(compositor, out var map)) + { + _ = map.Remove(key); + + map.Add(key, new WeakReference(value)); + } + else + { + map = new Dictionary> { [key] = new WeakReference(value) }; + + this.cache.Add(compositor, map); + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{T}.cs b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{T}.cs new file mode 100644 index 00000000000..8e686696e6d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/Cache/CompositionObjectCache{T}.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Helpers.Cache +{ + /// + /// A used to cache reusable instances in each UI thread + /// + /// The type of instances to cache + internal sealed class CompositionObjectCache + where T : CompositionObject + { + /// + /// The cache of weak references of type , to avoid memory leaks + /// + private readonly ConditionalWeakTable> cache = new ConditionalWeakTable>(); + + /// + /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found + /// + /// The current instance to get the value for + /// A instance used to produce a instance + /// A instance that is linked to + public T GetValue(Compositor compositor, Func producer) + { + lock (cache) + { + if (this.cache.TryGetValue(compositor, out var reference) && + reference.TryGetTarget(out var instance)) + { + return instance; + } + + // Create a new instance when needed + var fallback = producer(compositor); + this.cache.AddOrUpdate(compositor, new WeakReference(fallback)); + + return fallback; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs new file mode 100644 index 00000000000..213a714e588 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs @@ -0,0 +1,216 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Text; +using Microsoft.Graphics.Canvas.UI.Composition; +using Windows.Foundation; +using Windows.Graphics.DirectX; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Helpers +{ + /// + /// A delegate for load time effects. + /// + /// The bitmap. + /// The device. + /// The size target. + /// A CompositeDrawingSurface + public delegate CompositionDrawingSurface LoadTimeEffectHandler(CanvasBitmap bitmap, CompositionGraphicsDevice device, Size sizeTarget); + + /// + /// A that can load and draw images and other objects to Win2D surfaces and brushes + /// + public sealed partial class SurfaceLoader + { + /// + /// The cache of instances currently available + /// + private static readonly ConditionalWeakTable Instances = new ConditionalWeakTable(); + + /// + /// Gets a instance for the of the current window + /// + /// A instance to use in the current window + public static SurfaceLoader GetInstance() + { + return GetInstance(Window.Current.Compositor); + } + + /// + /// Gets a instance for a given + /// + /// The input object to use + /// A instance associated with + public static SurfaceLoader GetInstance(Compositor compositor) + { + lock (Instances) + { + if (Instances.TryGetValue(compositor, out var instance)) + { + return instance; + } + + instance = new SurfaceLoader(compositor); + + Instances.Add(compositor, instance); + + return instance; + } + } + + /// + /// The instance in use. + /// + private readonly Compositor compositor; + + /// + /// The instance in use. + /// + private CanvasDevice canvasDevice; + + /// + /// The instance to determinde which GPU is handling the request. + /// + private CompositionGraphicsDevice compositionDevice; + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use + private SurfaceLoader(Compositor compositor) + { + this.compositor = compositor; + + this.InitializeDevices(); + } + + /// + /// Reloads the and fields. + /// + private void InitializeDevices() + { + if (!(this.canvasDevice is null)) + { + this.canvasDevice.DeviceLost -= CanvasDevice_DeviceLost; + } + + if (!(this.compositionDevice is null)) + { + this.compositionDevice.RenderingDeviceReplaced -= CompositionDevice_RenderingDeviceReplaced; + } + + this.canvasDevice = new CanvasDevice(); + this.compositionDevice = CanvasComposition.CreateCompositionGraphicsDevice(this.compositor, this.canvasDevice); + + this.canvasDevice.DeviceLost += CanvasDevice_DeviceLost; + this.compositionDevice.RenderingDeviceReplaced += CompositionDevice_RenderingDeviceReplaced; + } + + /// + /// Invokes when the current is lost. + /// + private void CanvasDevice_DeviceLost(CanvasDevice sender, object args) + { + InitializeDevices(); + } + + /// + /// Invokes when the current changes rendering device. + /// + private void CompositionDevice_RenderingDeviceReplaced(CompositionGraphicsDevice sender, RenderingDeviceReplacedEventArgs args) + { + InitializeDevices(); + } + + /// + /// Loads an image from the URI. + /// + /// The URI. + /// + public async Task LoadFromUri(Uri uri) + { + return await LoadFromUri(uri, Size.Empty); + } + + /// + /// Loads an image from URI with a specified size. + /// + /// The URI. + /// The size target. + /// + public async Task LoadFromUri(Uri uri, Size sizeTarget) + { + var bitmap = await CanvasBitmap.LoadAsync(canvasDevice, uri); + var sizeSource = bitmap.Size; + + if (sizeTarget.IsEmpty) + { + sizeTarget = sizeSource; + } + + var surface = compositionDevice.CreateDrawingSurface( + sizeTarget, + DirectXPixelFormat.B8G8R8A8UIntNormalized, + DirectXAlphaMode.Premultiplied); + + using (var ds = CanvasComposition.CreateDrawingSession(surface)) + { + ds.Clear(Color.FromArgb(0, 0, 0, 0)); + ds.DrawImage(bitmap, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), new Rect(0, 0, sizeSource.Width, sizeSource.Height)); + } + + return surface; + } + + /// + /// Loads the text on to a . + /// + /// The text. + /// The size target. + /// The text format. + /// Color of the text. + /// Color of the bg. + /// + public CompositionDrawingSurface LoadText(string text, Size sizeTarget, CanvasTextFormat textFormat, Color textColor, Color bgColor) + { + var surface = compositionDevice.CreateDrawingSurface( + sizeTarget, + DirectXPixelFormat.B8G8R8A8UIntNormalized, + DirectXAlphaMode.Premultiplied); + + using (var ds = CanvasComposition.CreateDrawingSession(surface)) + { + ds.Clear(bgColor); + ds.DrawText(text, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), textColor, textFormat); + } + + return surface; + } + + /// + /// Loads an image from URI, with a specified size. + /// + /// The URI. + /// The size target. + /// The load effect handler callback. + /// + public async Task LoadFromUri(Uri uri, Size sizeTarget, LoadTimeEffectHandler loadEffectHandler) + { + if (loadEffectHandler != null) + { + var bitmap = await CanvasBitmap.LoadAsync(canvasDevice, uri); + return loadEffectHandler(bitmap, compositionDevice, sizeTarget); + } + + return await LoadFromUri(uri, sizeTarget); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.cs b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.cs new file mode 100644 index 00000000000..ebcb7104243 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Numerics; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.UI.Composition; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Microsoft.Toolkit.Uwp.UI.Media.Helpers.Cache; +using Windows.Foundation; +using Windows.Graphics.DirectX; +using Windows.Graphics.Display; +using Windows.Graphics.Imaging; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Helpers +{ + /// + /// A that can load and draw images and other objects to Win2D surfaces and brushes + /// + public sealed partial class SurfaceLoader + { + /// + /// Synchronization mutex to access the cache and load Win2D images concurrently + /// + private static readonly AsyncMutex Win2DMutex = new AsyncMutex(); + + /// + /// Gets the local cache mapping for previously loaded Win2D images + /// + private static readonly CompositionObjectCache Cache = new CompositionObjectCache(); + + /// + /// Loads a instance with the target image from the shared instance + /// + /// The path to the image to load + /// Indicates the desired DPI mode to use when loading the image + /// Indicates the cache option to use to load the image + /// A that returns the loaded instance + public static async Task LoadImageAsync(Uri uri, DpiMode dpiMode, CacheMode cacheMode = CacheMode.Default) + { + var compositor = Window.Current.Compositor; + + // Lock and check the cache first + using (await Win2DMutex.LockAsync()) + { + uri = uri.ToAppxUri(); + + if (cacheMode == CacheMode.Default && + Cache.TryGetValue(compositor, uri, out var cached)) + { + return cached; + } + + // Load the image + CompositionSurfaceBrush brush; + try + { + // This will throw and the canvas will re-initialize the Win2D device if needed + var sharedDevice = CanvasDevice.GetSharedDevice(); + brush = await LoadSurfaceBrushAsync(sharedDevice, compositor, uri, dpiMode); + } + catch + { + // Device error + brush = null; + } + + // Cache when needed and return the result + if (brush != null && + cacheMode != CacheMode.Disabled) + { + Cache.AddOrUpdate(compositor, uri, brush); + } + + return brush; + } + } + + /// + /// Loads a from the input , and prepares it to be used in a tile effect + /// + /// The device to use to process the Win2D image + /// The compositor instance to use to create the final brush + /// The path to the image to load + /// Indicates the desired DPI mode to use when loading the image + /// A that returns the loaded instance + private static async Task LoadSurfaceBrushAsync( + CanvasDevice canvasDevice, + Compositor compositor, + Uri uri, + DpiMode dpiMode) + { + var displayInformation = DisplayInformation.GetForCurrentView(); + float dpi = displayInformation.LogicalDpi; + + // Load the bitmap with the appropriate settings + using CanvasBitmap bitmap = dpiMode switch + { + DpiMode.UseSourceDpi => await CanvasBitmap.LoadAsync(canvasDevice, uri), + DpiMode.Default96Dpi => await CanvasBitmap.LoadAsync(canvasDevice, uri, 96), + DpiMode.DisplayDpi => await CanvasBitmap.LoadAsync(canvasDevice, uri, dpi), + DpiMode.DisplayDpiWith96AsLowerBound => await CanvasBitmap.LoadAsync(canvasDevice, uri, dpi >= 96 ? dpi : 96), + _ => throw new ArgumentOutOfRangeException(nameof(dpiMode), dpiMode, $"Invalid DPI mode: {dpiMode}") + }; + + // Calculate the surface size + Size + size = bitmap.Size, + sizeInPixels = new Size(bitmap.SizeInPixels.Width, bitmap.SizeInPixels.Height); + + // Get the device and the target surface + using CompositionGraphicsDevice graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice); + + // Create the drawing surface + var drawingSurface = graphicsDevice.CreateDrawingSurface( + sizeInPixels, + DirectXPixelFormat.B8G8R8A8UIntNormalized, + DirectXAlphaMode.Premultiplied); + + // Create a drawing session for the target surface + using (var drawingSession = CanvasComposition.CreateDrawingSession(drawingSurface, new Rect(0, 0, sizeInPixels.Width, sizeInPixels.Height), dpi)) + { + // Fill the target surface + drawingSession.Clear(Color.FromArgb(0, 0, 0, 0)); + drawingSession.DrawImage(bitmap, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, size.Width, size.Height)); + drawingSession.EffectTileSize = new BitmapSize { Width = (uint)size.Width, Height = (uint)size.Height }; + } + + // Setup the effect brush to use + var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface); + surfaceBrush.Stretch = CompositionStretch.None; + + double pixels = displayInformation.RawPixelsPerViewPixel; + + // Adjust the scale if the DPI scaling is greater than 100% + if (pixels > 1) + { + surfaceBrush.Scale = new Vector2((float)(1 / pixels)); + surfaceBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.NearestNeighbor; + } + + return surfaceBrush; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj index c0e7bba86e7..ca5aec12224 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj @@ -2,6 +2,7 @@ uap10.0.16299 + 8.0 Windows Community Toolkit UI Media This library provides UI brushes. It is part of the Windows Community Toolkit. @@ -23,6 +24,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/BrushProvider.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/BrushProvider.cs new file mode 100644 index 00000000000..df6c5042e33 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/BrushProvider.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A simple container used to store info on a custom composition effect to create + /// + public sealed class BrushProvider + { + /// + /// Gets the name of the target + /// + internal string Name { get; } + + /// + /// Gets the stored effect initializer + /// + internal Func> Initializer { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the target + /// The stored effect initializer + private BrushProvider(string name, Func> initializer) + { + this.Name = name; + this.Initializer = initializer; + } + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// A to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, CompositionBrush brush) => new BrushProvider(name, () => new ValueTask(brush)); + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// A instance that will produce the to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, Func factory) => new BrushProvider(name, () => new ValueTask(factory())); + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// An asynchronous instance that will produce the to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, Func> factory) => new BrushProvider(name, () => new ValueTask(factory())); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs new file mode 100644 index 00000000000..4011fb7d691 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs @@ -0,0 +1,688 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Windows.Graphics.Effects; +using Windows.UI; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// Adds a new to the current pipeline + /// + /// The blur amount to apply + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + async ValueTask Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial blur amount + /// The optional blur setter for the effect + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, out EffectSetter setter, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(GaussianBlurEffect.BlurAmount)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(GaussianBlurEffect.BlurAmount)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial blur amount + /// The optional blur animation for the effect + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, out EffectAnimation animation, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(GaussianBlurEffect.BlurAmount)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(GaussianBlurEffect.BlurAmount)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The saturation amount for the new effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation) + { + async ValueTask Factory() => new SaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect + /// The optional saturation setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new SaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(SaturationEffect.Saturation)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SaturationEffect.Saturation)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect + /// The optional saturation animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new SaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(SaturationEffect.Saturation)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SaturationEffect.Saturation)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity) + { + async ValueTask Factory() => new SepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The optional sepia intensity setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new SepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(SepiaEffect.Intensity)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SepiaEffect.Intensity)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The sepia intensity animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new SepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(SepiaEffect.Intensity)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SepiaEffect.Intensity)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity) + { + async ValueTask Factory() => new OpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The optional opacity setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new OpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(OpacityEffect.Opacity)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(OpacityEffect.Opacity)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The optional opacity animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new OpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(OpacityEffect.Opacity)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(OpacityEffect.Opacity)}" }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The amount of exposure to apply over the current effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount) + { + async ValueTask Factory() => new ExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect + /// The optional amount setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new ExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(ExposureEffect.Exposure)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(ExposureEffect.Exposure)}" }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect + /// The optional amount animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new ExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ExposureEffect.Exposure)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(ExposureEffect.Exposure)}" }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle) + { + async ValueTask Factory() => new HueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The optional rotation angle setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new HueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(HueRotationEffect.Angle)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(HueRotationEffect.Angle)}" }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The optional rotation angle animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new HueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(HueRotationEffect.Angle)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(HueRotationEffect.Angle)}" }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color) + { + async ValueTask Factory() => new TintEffect + { + Color = color, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new TintEffect + { + Color = color, + Source = await this.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(TintEffect.Color)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TintEffect.Color)}" }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new TintEffect + { + Color = color, + Source = await this.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TintEffect.Color)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TintEffect.Color)}" }); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use + /// The tint value to use + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint(float temperature, float tint) + { + async ValueTask Factory() => new TemperatureAndTintEffect + { + Temperature = temperature, + Tint = tint, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use + /// The optional temperature setter for the effect + /// The tint value to use + /// The optional tint setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint( + float temperature, + out EffectSetter temperatureSetter, + float tint, + out EffectSetter tintSetter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new TemperatureAndTintEffect + { + Temperature = temperature, + Tint = tint, + Source = await this.sourceProducer(), + Name = id + }; + + temperatureSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", value); + + tintSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(TemperatureAndTintEffect.Tint)}", value); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(TemperatureAndTintEffect.Tint)}" }); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use + /// The optional temperature animation for the effect + /// The tint value to use + /// The optional tint animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint( + float temperature, + out EffectAnimation temperatureAnimation, + float tint, + out EffectAnimation tintAnimation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new TemperatureAndTintEffect + { + Temperature = temperature, + Tint = tint, + Source = await this.sourceProducer(), + Name = id + }; + + temperatureAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", value, duration); + + tintAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TemperatureAndTintEffect.Tint)}", value, duration); + + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(TemperatureAndTintEffect.Tint)}" }); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The amount of mix to apply over the current effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade(Color color, float mix) + { + return FromColor(color).CrossFade(this, mix); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The optional color setter for the effect + /// The initial amount of mix to apply over the current effect + /// The optional mix setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade( + Color color, + out EffectSetter colorSetter, + float mix, + out EffectSetter mixSetter) + { + return FromColor(color, out colorSetter).CrossFade(this, mix, out mixSetter); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The optional color animation for the effect + /// The initial amount of mix to apply over the current effect + /// The optional mix animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade( + Color color, + out EffectAnimation colorAnimation, + float mix, + out EffectAnimation mixAnimation) + { + return FromColor(color, out colorAnimation).CrossFade(this, mix, out mixAnimation); + } + + /// + /// Applies a luminance to alpha effect on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder LuminanceToAlpha() + { + async ValueTask Factory() => new LuminanceToAlphaEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies an invert effect on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Invert() + { + async ValueTask Factory() => new InvertEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a grayscale on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Grayscale() + { + async ValueTask Factory() => new GrayscaleEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a custom effect to the current pipeline + /// + /// A that takes the current instance and produces a new effect to display + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Effect( + Func factory, + IEnumerable animations = null, + IEnumerable initializers = null) + { + async ValueTask Factory() => factory(await this.sourceProducer()); + + return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + + /// + /// Applies a custom effect to the current pipeline + /// + /// An asynchronous that takes the current instance and produces a new effect to display + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Effect( + Func> factory, + IEnumerable animations = null, + IEnumerable initializers = null) + { + async ValueTask Factory() => await factory(await this.sourceProducer()); + + return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Initialization.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Initialization.cs new file mode 100644 index 00000000000..02439a5f37a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Initialization.cs @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Microsoft.Toolkit.Uwp.UI.Media.Helpers; +using Microsoft.Toolkit.Uwp.UI.Media.Helpers.Cache; +using Windows.Graphics.Effects; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// The cache manager for backdrop brushes + /// + private static readonly CompositionObjectCache BackdropBrushCache = new CompositionObjectCache(); + + /// + /// The cache manager for host backdrop brushes + /// + private static readonly CompositionObjectCache HostBackdropBrushCache = new CompositionObjectCache(); + + /// + /// Starts a new pipeline from the returned by + /// + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdrop() + { + ValueTask Factory() + { + var brush = BackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateBackdropBrush()); + + return new ValueTask(brush); + } + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from the returned by + /// + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdrop() + { + ValueTask Factory() + { + var brush = HostBackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateHostBackdropBrush()); + + return new ValueTask(brush); + } + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color) + { + return new PipelineBuilder(() => new ValueTask(new ColorSourceEffect { Color = color })); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(new ColorSourceEffect + { + Color = color, + Name = id + }); + + setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(ColorSourceEffect.Color)}", value); + + return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.Color)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(new ColorSourceEffect + { + Color = color, + Name = id + }); + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ColorSourceEffect.Color)}", value, duration); + + return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.Color)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color) + { + return new PipelineBuilder(() => new ValueTask(new ColorSourceEffect { ColorHdr = color })); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(new ColorSourceEffect + { + ColorHdr = color, + Name = id + }); + + setter = (brush, value) => brush.Properties.InsertVector4($"{id}.{nameof(ColorSourceEffect.ColorHdr)}", value); + + return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.ColorHdr)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(new ColorSourceEffect + { + ColorHdr = color, + Name = id + }); + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ColorSourceEffect.ColorHdr)}", value, duration); + + return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.ColorHdr)}" }); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(CompositionBrush brush) + { + return new PipelineBuilder(() => new ValueTask(brush)); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that synchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(Func factory) + { + return new PipelineBuilder(() => new ValueTask(factory())); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that asynchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(Func> factory) + { + async ValueTask Factory() => await factory(); + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(IGraphicsEffectSource effect) + { + return new PipelineBuilder(() => new ValueTask(effect)); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that synchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(Func factory) + { + return new PipelineBuilder(() => new ValueTask(factory())); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that asynchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(Func> factory) + { + async ValueTask Factory() => await factory(); + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from a Win2D image + /// + /// The relative path for the image to load (eg. "/Assets/image.png") + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromImage(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return FromImage(relativePath.ToAppxUri(), dpiMode, cacheMode); + } + + /// + /// Starts a new pipeline from a Win2D image + /// + /// The path for the image to load + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromImage(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return new PipelineBuilder(async () => await SurfaceLoader.LoadImageAsync(uri, dpiMode, cacheMode)); + } + + /// + /// Starts a new pipeline from a Win2D image tiled to cover the available space + /// + /// The relative path for the image to load (eg. "/Assets/image.png") + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromTiles(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return FromTiles(relativePath.ToAppxUri(), dpiMode, cacheMode); + } + + /// + /// Starts a new pipeline from a Win2D image tiled to cover the available space + /// + /// The path for the image to load + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromTiles(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + var image = FromImage(uri, dpiMode, cacheMode); + + async ValueTask Factory() => new BorderEffect + { + ExtendX = CanvasEdgeBehavior.Wrap, + ExtendY = CanvasEdgeBehavior.Wrap, + Source = await image.sourceProducer() + }; + + return new PipelineBuilder(image, Factory); + } + + /// + /// Starts a new pipeline from the returned by on the input + /// + /// The source to use to create the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromUIElement(UIElement element) + { + return new PipelineBuilder(() => new ValueTask(ElementCompositionPreview.GetElementVisual(element).Compositor.CreateBackdropBrush())); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs new file mode 100644 index 00000000000..a9dcb621367 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Windows.Graphics.Effects; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// Gets the aligned pipelines to merge for a given operation + /// + /// The left pipeline to merge + /// The right pipeline to merge + /// The placemeht to use with the two input pipelines + /// A instance with the aligned pipelines + [Pure] + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008", Justification = "ValueTuple return type")] + private static (PipelineBuilder Foreground, PipelineBuilder Background) GetMergePipeline( + PipelineBuilder left, + PipelineBuilder right, + Placement placement) + { + switch (placement) + { + case Placement.Foreground: + return (left, right); + case Placement.Background: + return (right, left); + default: + throw new ArgumentException($"Invalid placement value: {placement}"); + } + } + + /// + /// Blends two pipelines using a instance with the specified mode + /// + /// The second instance to blend + /// The desired to use to blend the input pipelines + /// The placemeht to use with the two input pipelines + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blend(PipelineBuilder pipeline, BlendEffectMode mode, Placement placement = Placement.Foreground) + { + var pipelines = GetMergePipeline(this, pipeline, placement); + + async ValueTask Factory() => new BlendEffect + { + Foreground = await pipelines.Foreground.sourceProducer(), + Background = await pipelines.Background.sourceProducer(), + Mode = mode + }; + + return new PipelineBuilder(Factory, pipelines.Foreground, pipelines.Background); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects + /// The placement to use with the two input pipelines + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor = 0.5f, Placement placement = Placement.Foreground) + { + var pipelines = GetMergePipeline(this, pipeline, placement); + + async ValueTask Factory() => new CrossFadeEffect + { + CrossFade = factor, + Source1 = await pipelines.Foreground.sourceProducer(), + Source2 = await pipelines.Background.sourceProducer() + }; + + return new PipelineBuilder(Factory, pipelines.Foreground, pipelines.Background); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects + /// The optional blur setter for the effect + /// The placement to use with the two input pipelines + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectSetter setter, Placement placement = Placement.Foreground) + { + var pipelines = GetMergePipeline(this, pipeline, placement); + + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new CrossFadeEffect + { + CrossFade = factor, + Source1 = await pipelines.Foreground.sourceProducer(), + Source2 = await pipelines.Background.sourceProducer(), + Name = id + }; + + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CrossFadeEffect.CrossFade)}", value); + + return new PipelineBuilder(Factory, pipelines.Foreground, pipelines.Background, new[] { $"{id}.{nameof(CrossFadeEffect.CrossFade)}" }); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects + /// The optional blur animation for the effect + /// The placement to use with the two input pipelines + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectAnimation animation, Placement placement = Placement.Foreground) + { + var pipelines = GetMergePipeline(this, pipeline, placement); + + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask Factory() => new CrossFadeEffect + { + CrossFade = factor, + Source1 = await pipelines.Foreground.sourceProducer(), + Source2 = await pipelines.Background.sourceProducer(), + Name = id + }; + + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CrossFadeEffect.CrossFade)}", value, duration); + + return new PipelineBuilder(Factory, pipelines.Foreground, pipelines.Background, new[] { $"{id}.{nameof(CrossFadeEffect.CrossFade)}" }); + } + + /// + /// Blends two pipelines using the provided to do so + /// + /// The blend function to use + /// The background pipeline to blend with the current instance + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Merge( + Func factory, + PipelineBuilder background, + IEnumerable animations = null, + IEnumerable initializers = null) + { + async ValueTask Factory() => factory(await this.sourceProducer(), await background.sourceProducer()); + + return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + + /// + /// Blends two pipelines using the provided asynchronous to do so + /// + /// The asynchronous blend function to use + /// The background pipeline to blend with the current instance + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Merge( + Func> factory, + PipelineBuilder background, + IEnumerable animations = null, + IEnumerable initializers = null) + { + async ValueTask Factory() => await factory(await this.sourceProducer(), await background.sourceProducer()); + + return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Prebuilt.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Prebuilt.cs new file mode 100644 index 00000000000..c5782b19ff5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Prebuilt.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.Graphics.Canvas.Effects; +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The amount of tint to apply over the current effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdropAcrylic( + Color tint, + float mix, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromHostBackdrop() + .LuminanceToAlpha() + .Opacity(0.4f) + .Blend(FromHostBackdrop(), BlendEffectMode.Multiply) + .Shade(tint, mix); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color setter for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix setter for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdropAcrylic( + Color tint, + out EffectSetter tintSetter, + float mix, + out EffectSetter mixSetter, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromHostBackdrop() + .LuminanceToAlpha() + .Opacity(0.4f) + .Blend(FromHostBackdrop(), BlendEffectMode.Multiply) + .Shade(tint, out tintSetter, mix, out mixSetter); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color animation for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix animation for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdropAcrylic( + Color tint, + out EffectAnimation tintAnimation, + float mix, + out EffectAnimation mixAnimation, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromHostBackdrop() + .LuminanceToAlpha() + .Opacity(0.4f) + .Blend(FromHostBackdrop(), BlendEffectMode.Multiply) + .Shade(tint, out tintAnimation, mix, out mixAnimation); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The amount of tint to apply over the current effect + /// The amount of blur to apply to the acrylic brush + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdropAcrylic( + Color tint, + float mix, + float blur, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromBackdrop() + .Shade(tint, mix) + .Blur(blur); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color setter for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix setter for the effect + /// The amount of blur to apply to the acrylic brush + /// The optional blur setter for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdropAcrylic( + Color tint, + out EffectSetter tintSetter, + float mix, + out EffectSetter mixSetter, + float blur, + out EffectSetter blurSetter, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromBackdrop() + .Shade(tint, out tintSetter, mix, out mixSetter) + .Blur(blur, out blurSetter); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color animation for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix animation for the effect + /// The amount of blur to apply to the acrylic brush + /// The optional blur animation for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdropAcrylic( + Color tint, + out EffectAnimation tintAnimation, + float mix, + out EffectAnimation mixAnimation, + float blur, + out EffectAnimation blurAnimation, + Uri noiseUri, + CacheMode cacheMode = CacheMode.Default) + { + var pipeline = + FromBackdrop() + .Shade(tint, out tintAnimation, mix, out mixAnimation) + .Blur(blur, out blurAnimation); + + if (noiseUri != null) + { + return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay, Placement.Background); + } + + return pipeline; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs new file mode 100644 index 00000000000..e6568aeee26 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Windows.Graphics.Effects; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that represents a custom effect property setter that can be applied to a + /// + /// The type of property value to set + /// The target instance to target + /// The property value to set + public delegate void EffectSetter(CompositionBrush brush, T value) + where T : unmanaged; + + /// + /// A that represents a custom effect property animation that can be applied to a + /// + /// The type of property value to animate + /// The target instance to use to start the animation + /// The animation target value + /// The animation duration + /// A that completes when the target animation completes + public delegate Task EffectAnimation(CompositionBrush brush, T value, TimeSpan duration) + where T : unmanaged; + + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// The instance used to produce the output for this pipeline + /// + private readonly Func> sourceProducer; + + /// + /// The collection of animation properties present in the current pipeline + /// + private readonly IReadOnlyCollection animationProperties; + + /// + /// The collection of info on the parameters that need to be initialized after creating the final + /// + private readonly IReadOnlyDictionary>> lazyParameters; + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will return the initial + private PipelineBuilder(Func> factory) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + this.sourceProducer = () => new ValueTask(new CompositionEffectSourceParameter(id)); + this.animationProperties = Array.Empty(); + this.lazyParameters = new Dictionary>> { { id, factory } }; + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will return the initial + private PipelineBuilder(Func> factory) + : this( + factory, + Array.Empty(), + new Dictionary>>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + private PipelineBuilder( + Func> factory, + IReadOnlyCollection animations) + : this( + factory, + animations, + new Dictionary>>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + Func> factory, + IReadOnlyCollection animations, + IReadOnlyDictionary>> lazy) + { + this.sourceProducer = factory; + this.animationProperties = animations; + this.lazyParameters = lazy; + } + + /// + /// Initializes a new instance of the class. + /// + /// The source pipeline to attach the new effect to + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + PipelineBuilder source, + Func> factory, + IReadOnlyCollection animations = null, + IReadOnlyDictionary>> lazy = null) + : this( + factory, + animations?.Merge(source.animationProperties) ?? source.animationProperties, + lazy?.Merge(source.lazyParameters) ?? source.lazyParameters) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The first pipeline to merge + /// The second pipeline to merge + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + Func> factory, + PipelineBuilder a, + PipelineBuilder b, + IReadOnlyCollection animations = null, + IReadOnlyDictionary>> lazy = null) + : this( + factory, + animations?.Merge(a.animationProperties.Merge(b.animationProperties)) ?? a.animationProperties.Merge(b.animationProperties), + lazy?.Merge(a.lazyParameters.Merge(b.lazyParameters)) ?? a.lazyParameters.Merge(b.lazyParameters)) + { + } + + /// + /// Builds a instance from the current effects pipeline + /// + /// A that returns the final instance to use + [Pure] + public async Task BuildAsync() + { + var effect = await this.sourceProducer() as IGraphicsEffect; + + // Validate the pipeline + if (effect is null) + { + throw new InvalidOperationException("The pipeline doesn't contain a valid effects sequence"); + } + + // Build the effects factory + var factory = this.animationProperties.Count > 0 + ? Window.Current.Compositor.CreateEffectFactory(effect, this.animationProperties) + : Window.Current.Compositor.CreateEffectFactory(effect); + + // Create the effect factory and apply the final effect + var effectBrush = factory.CreateBrush(); + foreach (var pair in this.lazyParameters) + { + effectBrush.SetSourceParameter(pair.Key, await pair.Value()); + } + + return effectBrush; + } + + /// + /// Builds the current pipeline and creates a that is applied to the input + /// + /// The target to apply the brush to + /// An optional to use to bind the size of the created brush + /// A that returns the final instance to use + public async Task AttachAsync(UIElement target, UIElement reference = null) + { + var visual = Window.Current.Compositor.CreateSpriteVisual(); + + visual.Brush = await this.BuildAsync(); + + ElementCompositionPreview.SetElementChildVisual(target, visual); + + if (reference != null) + { + visual.BindSize(reference); + } + + return visual; + } + + /// + /// Creates a new from the current effects pipeline + /// + /// A instance ready to be displayed + [Pure] + public XamlCompositionBrush AsBrush() + { + return new XamlCompositionBrush(this); + } + } +} diff --git a/readme.md b/readme.md index 8170c138e27..d1f3e7a6799 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,6 @@ Once you do a search, you should see a list similar to the one below (versions m ## Features The [Features list](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/toc.md#controls) refers to all the currently available features that can be found in the Windows Community Toolkit. Most features should work with the Falls Creator Update SDK 16299 and above; however, refer to specific documentation on each feature for more information. - ## Feedback and Requests Please use [GitHub Issues](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues) for bug reports and feature requests. For general questions and support, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag `windows-community-toolkit`.