Skip to content

Commit

Permalink
Merge pull request #3 from Sergio0694/feature_acrylic_brushes
Browse files Browse the repository at this point in the history
Feature acrylic brushes
  • Loading branch information
Sergio0694 authored Jul 12, 2017
2 parents 9360c9c + bdaa02e commit d2d58cb
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 3 deletions.
9 changes: 9 additions & 0 deletions UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve
private static async Task<CompositionSurfaceBrush> LoadWin2DSurfaceBrushFromImageAsync(
[NotNull] Compositor compositor, [NotNull] Uri uri, bool reload = false)
{
// Fix the Uri if it has been generated by the XAML designer
if (uri.Scheme.Equals("ms-resource"))
{
String path = uri.AbsolutePath.StartsWith("/Files")
? uri.AbsolutePath.Replace("/Files", String.Empty)
: uri.AbsolutePath;
uri = new Uri($"ms-appx://{path}");
}

// Lock the semaphore and check the cache first
await Win2DSemaphore.WaitAsync();
if (!reload && SurfacesCache.TryGetValue(uri.ToString(), out CompositionSurfaceBrush cached))
Expand Down
276 changes: 276 additions & 0 deletions UICompositionAnimations/Brushes/CustomAcrylicBrush.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Effects;
using Windows.UI;
using Microsoft.Graphics.Canvas.Effects;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using UICompositionAnimations.Behaviours;
using UICompositionAnimations.Behaviours.Effects;
using UICompositionAnimations.Helpers;

namespace UICompositionAnimations.Brushes
{
/// <summary>
/// A custom XAML brush that includes an acrylic effect that blurs the in-app content
/// </summary>
public sealed class CustomAcrylicBrush : XamlCompositionBrushBase
{
#region Constants

// The animation of the blur transition animation
private const int BlurAnimationDuration = 250;

// The name of the animatable blur amount property
private const String BlurAmountParameterName = "Blur.BlurAmount";

// The name of the animatable source 1 property (the brush) of the tint effect
private const String TintColor1ParameterName = "Tint.Source1Amount";

// The name of the animatable source 2 property (the tint color) of the tint effect
private const String TintColor2ParameterName = "Tint.Source2Amount";

// The name of the animatable color property of the color effect
private const String ColorSourceColorParameterName = "ColorSource.Color";

#endregion

#region Properties

/// <summary>
/// Gets or sets the source mode for the custom acrylic effect
/// </summary>
public AcrylicEffectMode Mode
{
get { return GetValue(ModeProperty).To<AcrylicEffectMode>(); }
set { SetValue(ModeProperty, value); }
}

/// <summary>
/// Gets the <see cref="DependencyProperty"/> for the <see cref="Mode"/> property
/// </summary>
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register(nameof(Mode), typeof(AcrylicEffectMode), typeof(LightingBrush), new PropertyMetadata(AcrylicEffectMode.InAppBlur, OnModePropertyChanged));

private static async void OnModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomAcrylicBrush @this = d.To<CustomAcrylicBrush>();
await @this.ConnectedSemaphore.WaitAsync();
if (@this.CompositionBrush != null)
{
// Rebuild the effects pipeline when needed
await @this.SetupEffectAsync();
}
@this.ConnectedSemaphore.Release();
}

/// <summary>
/// Gets or sets the blur amount for the effect (NOTE: this property is ignored when the active mode is <see cref="AcrylicEffectMode.HostBackdrop"/>)
/// </summary>
public double BlurAmount
{
get { return GetValue(BlurAmountProperty).To<double>(); }
set { SetValue(BlurAmountProperty, value); }
}

/// <summary>
/// Gets the <see cref="DependencyProperty"/> for the <see cref="BlurAmount"/> property
/// </summary>
public static readonly DependencyProperty BlurAmountProperty =
DependencyProperty.Register(nameof(BlurAmount), typeof(double), typeof(LightingBrush), new PropertyMetadata(8, OnBlurAmountPropertyChanged));

private static async void OnBlurAmountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomAcrylicBrush @this = d.To<CustomAcrylicBrush>();
await @this.ConnectedSemaphore.WaitAsync();
if (@this.Mode == AcrylicEffectMode.InAppBlur)
@this._EffectBrush?.StartAnimationAsync(BlurAmountParameterName, (float)e.NewValue.To<double>(), TimeSpan.FromMilliseconds(BlurAnimationDuration)).Forget();
@this.ConnectedSemaphore.Release();

}

/// <summary>
/// Gets or sets the color for the tint effect
/// </summary>
public Color Tint
{
get { return GetValue(TintProperty).To<Color>(); }
set { SetValue(TintProperty, value); }
}

/// <summary>
/// Gets the <see cref="DependencyProperty"/> for the <see cref="Tint"/> property
/// </summary>
public static readonly DependencyProperty TintProperty =
DependencyProperty.Register(nameof(Tint), typeof(Color), typeof(LightingBrush), new PropertyMetadata(Colors.Transparent, OnTintPropertyChanged));

private static async void OnTintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomAcrylicBrush @this = d.To<CustomAcrylicBrush>();
await @this.ConnectedSemaphore.WaitAsync();
if (@this.CompositionBrush != null)
{
d.To<CustomAcrylicBrush>()?._EffectBrush.Properties.InsertColor(ColorSourceColorParameterName, e.NewValue.To<Color>());
}
@this.ConnectedSemaphore.Release();
}

/// <summary>
/// Gets or sets the color for the tint effect (NOTE: this value must be in the [0..1) range)
/// </summary>
public double TintMix
{
get { return GetValue(TintMixProperty).To<double>(); }
set { SetValue(TintMixProperty, value); }
}

/// <summary>
/// Gets the <see cref="DependencyProperty"/> for the <see cref="TintMix"/> property
/// </summary>
public static readonly DependencyProperty TintMixProperty =
DependencyProperty.Register(nameof(Tint), typeof(double), typeof(LightingBrush), new PropertyMetadata(0, OnTintMixPropertyChanged));

private static async void OnTintMixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double value = e.NewValue.To<double>();
float fvalue = (float)value;
if (value < 0 || value >= 1) throw new ArgumentOutOfRangeException("The tint mix must be in the [0..1) range");
CustomAcrylicBrush @this = d.To<CustomAcrylicBrush>();
await @this.ConnectedSemaphore.WaitAsync();
if (@this.CompositionBrush != null)
{
@this._EffectBrush.Properties.InsertScalar(TintColor1ParameterName, 1 - fvalue);
@this._EffectBrush.Properties.InsertScalar(TintColor2ParameterName, fvalue);
}
@this.ConnectedSemaphore.Release();
}

/// <summary>
/// Gets the <see cref="Uri"/> to the noise texture to use (NOTE: this must be initialized before using the brush)
/// </summary>
public Uri NoiseTextureUri { get; set; }

#endregion

// Initialization semaphore (due to the Win2D image loading being asynchronous)
private readonly SemaphoreSlim ConnectedSemaphore = new SemaphoreSlim(1);

// The composition brush used to render the effect
private CompositionEffectBrush _EffectBrush;

/// <inheritdoc cref="XamlCompositionBrushBase"/>
protected override async void OnConnected()
{
if (CompositionBrush == null)
{
await ConnectedSemaphore.WaitAsync();
if (CompositionBrush == null) // It could have been initialized while waiting on the semaphore
{
await SetupEffectAsync();
}
ConnectedSemaphore.Release();
}
base.OnConnected();
}

/// <inheritdoc cref="XamlCompositionBrushBase"/>
protected override async void OnDisconnected()
{
if (CompositionBrush != null)
{
await ConnectedSemaphore.WaitAsync();
if (CompositionBrush != null)
{
CompositionBrush.Dispose();
CompositionBrush = null;
}
ConnectedSemaphore.Release();
}
base.OnDisconnected();
}

/// <summary>
/// Initializes the appropriate acrylic effect for the current instance
/// </summary>
private async Task SetupEffectAsync()
{
// Dictionary to track the reference and animatable parameters
IDictionary<String, CompositionBrush> sourceParameters = new Dictionary<String, CompositionBrush>();
List<String> animatableParameters = new List<String>
{
TintColor1ParameterName,
TintColor2ParameterName,
ColorSourceColorParameterName
};

// Setup the base effect
IGraphicsEffectSource baseEffect;
if (Mode == AcrylicEffectMode.InAppBlur)
{
// Prepare a luminosity to alpha effect to adjust the background contrast
CompositionBackdropBrush backdropBrush = Window.Current.Compositor.CreateBackdropBrush();
baseEffect = new GaussianBlurEffect
{
Name = "Blur",
BlurAmount = 0f,
BorderMode = EffectBorderMode.Hard,
Optimization = EffectOptimization.Balanced,
Source = new CompositionEffectSourceParameter(nameof(backdropBrush))
};
animatableParameters.Add(BlurAmountParameterName);
sourceParameters.Add(nameof(backdropBrush), backdropBrush);
}
else
{
// Prepare a luminosity to alpha effect to adjust the background contrast
CompositionBackdropBrush hostBackdropBrush = Window.Current.Compositor.CreateHostBackdropBrush();
CompositionEffectSourceParameter backgroundParameter = new CompositionEffectSourceParameter(nameof(hostBackdropBrush));
LuminanceToAlphaEffect alphaEffect = new LuminanceToAlphaEffect { Source = backgroundParameter };
OpacityEffect opacityEffect = new OpacityEffect
{
Source = alphaEffect,
Opacity = 0.4f // Reduce the amount of the effect to avoid making bright areas completely black
};

// Layer [0,1,3] - Desktop background with blur and tint overlay
baseEffect = new BlendEffect
{
Background = backgroundParameter,
Foreground = opacityEffect,
Mode = BlendEffectMode.Overlay
};
sourceParameters.Add(nameof(hostBackdropBrush), hostBackdropBrush);
}

// Get the noise brush using Win2D
IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(Window.Current.Compositor,
baseEffect, sourceParameters, Tint, (float)TintMix, null, NoiseTextureUri);

// Extract and setup the tint and color effects
ArithmeticCompositeEffect tint = source as ArithmeticCompositeEffect ?? source.To<BlendEffect>().Background as ArithmeticCompositeEffect;
if (tint == null) throw new InvalidOperationException("Error while retrieving the tint effect");
tint.Name = "Tint";
ColorSourceEffect color = tint.Source2 as ColorSourceEffect;
if (color == null) throw new InvalidOperationException("Error while retrieving the color effect");
color.Name = "ColorSource";

// Make sure the Win2D brush was loaded correctly
CompositionEffectFactory factory = Window.Current.Compositor.CreateEffectFactory(source, animatableParameters);

// Create the effect factory and apply the final effect
_EffectBrush = factory.CreateBrush();
foreach (KeyValuePair<String, CompositionBrush> pair in sourceParameters)
{
_EffectBrush.SetSourceParameter(pair.Key, pair.Value);
}

// Animate the blur and store the effect
if (Mode == AcrylicEffectMode.InAppBlur)
_EffectBrush.StartAnimationAsync(BlurAmountParameterName, (float)BlurAmount, TimeSpan.FromMilliseconds(BlurAnimationDuration)).Forget();
CompositionBrush = _EffectBrush;
}
}
}
2 changes: 1 addition & 1 deletion UICompositionAnimations/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.6.4.0")]
[assembly: AssemblyVersion("2.7.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
1 change: 1 addition & 0 deletions UICompositionAnimations/UICompositionAnimations.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<Compile Include="Behaviours\Effects\AttachedStaticCompositionEffect.cs" />
<Compile Include="Behaviours\Misc\CompositionAnimationParameters.cs" />
<Compile Include="Behaviours\Misc\FixedAnimationType.cs" />
<Compile Include="Brushes\CustomAcrylicBrush.cs" />
<Compile Include="Brushes\LightingBrush.cs" />
<Compile Include="CompositionExtensions.cs" />
<Compile Include="Composition\CompositionManager.cs" />
Expand Down
4 changes: 2 additions & 2 deletions UICompositionAnimations/UICompositionAnimations.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<package >
<metadata>
<id>Sergio0694.UWP.UICompositionAnimations</id>
<version>2.6.4.0</version>
<version>2.7.0.0</version>
<title>UICompositionAnimations</title>
<description>A wrapper UWP PCL to work with Windows.UI.Composition and XAML animations, and Win2D effects</description>
<authors>Sergio Pedri</authors>
<owners>Sergio Pedri</owners>
<projectUrl>https://github.com/Sergio0694/UICompositionAnimations</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>Added attenuation property to the SpotLight, minor refactoring</releaseNotes>
<releaseNotes>Added XAML custom acrylic brush</releaseNotes>
<copyright>Copyright 2017</copyright>
<tags>uwp composition animations xaml csharp windows winrt universal app ui win2d graphics</tags>
</metadata>
Expand Down

0 comments on commit d2d58cb

Please sign in to comment.