Skip to content

Instantly share code, notes, and snippets.

@Sergio0694
Last active April 22, 2022 17:04
Show Gist options
  • Save Sergio0694/5219d16bd38b965a4c095b3abf94c465 to your computer and use it in GitHub Desktop.
Save Sergio0694/5219d16bd38b965a4c095b3abf94c465 to your computer and use it in GitHub Desktop.
A method that combines Composition APIs and Win2D to replicate the Acrylic brush on Windows 10 Creator's Update
/// <summary>
/// Gets a shared semaphore to avoid loading multiple Win2D resources at the same time
/// </summary>
private static readonly SemaphoreSlim Win2DSemaphore = new SemaphoreSlim(1);
/// <summary>
/// Creates an effect brush that's similar to the official Acrylic brush in the Fall Creator's Update.
/// The pipeline uses the following effects: HostBackdropBrush > <see cref="LuminanceToAlphaEffect"/> >
/// <see cref="OpacityEffect"/> > <see cref="BlendEffect"/> > <see cref="ArithmeticCompositeEffect"/> >
/// <see cref="ColorSourceEffect"/> > <see cref="BorderEffect"/> with customizable blend factors for each couple of layers
/// </summary>
/// <typeparam name="T">The type of the target element that will host the resulting <see cref="SpriteVisual"/></typeparam>
/// <param name="element">The target element that will host the effect</param>
/// <param name="color">The tint color for the effect</param>
/// <param name="colorMix">The opacity of the color over the blurred background</param>
/// <param name="canvas">The source <see cref="CanvasControl"/> to generate the noise image using Win2D</param>
/// <param name="uri">The path of the noise image to use</param>
/// <param name="timeThreshold">The maximum time to wait for the Win2D device to be restored in case of initial failure/></param>
public static async Task<SpriteVisual> GetAttachedSemiAcrylicEffectAsync<T>(
[NotNull] this T element, Color color, float colorMix,
[NotNull] CanvasControl canvas, [NotNull] Uri uri, int timeThreshold = 1000) where T : FrameworkElement
{
// Percentage check
if (colorMix <= 0 || colorMix >= 1) throw new ArgumentOutOfRangeException("The mix factors must be in the [0,1] range");
if (timeThreshold <= 0) throw new ArgumentOutOfRangeException("The time threshold must be a positive number");
// Setup the compositor
Visual visual = ElementCompositionPreview.GetElementVisual(element);
Compositor compositor = visual.Compositor;
// Prepare a luminosity to alpha effect to adjust the background contrast
CompositionBackdropBrush hostBackdropBrush = 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
BlendEffect alphaBlend = new BlendEffect
{
Background = backgroundParameter,
Foreground = opacityEffect,
Mode = BlendEffectMode.Overlay
};
ArithmeticCompositeEffect composite = new ArithmeticCompositeEffect
{
MultiplyAmount = 0,
Source1Amount = 1 - colorMix,
Source2Amount = colorMix, // Mix the background with the desired tint color
Source1 = alphaBlend,
Source2 = new ColorSourceEffect { Color = color }
};
IDictionary<String, CompositionBrush> sourceParameters = new Dictionary<String, CompositionBrush>
{
{ nameof(hostBackdropBrush), hostBackdropBrush }
};
// Get the noise brush using Win2D
TaskCompletionSource<CompositionSurfaceBrush> tcs = new TaskCompletionSource<CompositionSurfaceBrush>();
async Task<CompositionSurfaceBrush> LoadImageAsync(bool shouldThrow)
{
// Load the image - this will only succeed when there's an available Win2D device
try
{
using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(canvas, uri))
{
// Get the device and the target surface
CompositionGraphicsDevice device = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvas.Device);
CompositionDrawingSurface surface = device.CreateDrawingSurface(default(Size),
DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
// Calculate the surface size
Size size = bitmap.Size;
CanvasComposition.Resize(surface, size);
// Draw the image on the surface and get the resulting brush
using (CanvasDrawingSession session = CanvasComposition.CreateDrawingSession(surface))
{
session.Clear(Color.FromArgb(0, 0, 0, 0));
session.DrawImage(bitmap, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, size.Width, size.Height));
CompositionSurfaceBrush brush = surface.Compositor.CreateSurfaceBrush(surface);
return brush;
}
}
}
catch when (!shouldThrow)
{
// Win2D error, just ignore and continue
return null;
}
}
async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
// Cancel previous actions
args.GetTrackedAction()?.Cancel();
// Load the image and notify the canvas
Task<CompositionSurfaceBrush> task = LoadImageAsync(false);
IAsyncAction action = task.AsAsyncAction();
try
{
args.TrackAsyncAction(action);
CompositionSurfaceBrush brush = await task;
action.Cancel();
tcs.TrySetResult(brush);
}
catch (COMException)
{
// Somehow another action was still being tracked
tcs.TrySetResult(null);
}
}
await Win2DSemaphore.WaitAsync();
canvas.CreateResources += Canvas_CreateResources;
try
{
// This will throw and the canvas will re-initialize the Win2D device if needed
await LoadImageAsync(true);
}
catch (ArgumentException)
{
// Just ignore here
}
catch
{
// Win2D messed up big time
tcs.TrySetResult(null);
}
await Task.WhenAny(tcs.Task, Task.Delay(timeThreshold).ContinueWith(t => tcs.TrySetResult(null)));
CompositionSurfaceBrush noiseBitmap = tcs.Task.Result;
canvas.CreateResources -= Canvas_CreateResources;
Win2DSemaphore.Release();
// Make sure the Win2D brush was loaded correctly
CompositionEffectFactory factory;
if (noiseBitmap != null)
{
// Layer 4 - Noise effect
BorderEffect borderEffect = new BorderEffect
{
ExtendX = CanvasEdgeBehavior.Wrap,
ExtendY = CanvasEdgeBehavior.Wrap,
Source = new CompositionEffectSourceParameter(nameof(noiseBitmap))
};
BlendEffect blendEffect = new BlendEffect
{
Background = composite,
Foreground = borderEffect,
Mode = BlendEffectMode.Overlay
};
factory = compositor.CreateEffectFactory(blendEffect);
sourceParameters.Add(nameof(noiseBitmap), noiseBitmap);
}
else
{
// Fallback, just use the first layers
factory = compositor.CreateEffectFactory(composite);
}
// Create the effect factory and apply the final effect
CompositionEffectBrush effectBrush = factory.CreateBrush();
foreach (KeyValuePair<String, CompositionBrush> pair in sourceParameters)
{
effectBrush.SetSourceParameter(pair.Key, pair.Value);
}
// Create the sprite to display and add it to the visual tree
SpriteVisual sprite = compositor.CreateSpriteVisual();
sprite.Brush = effectBrush;
sprite.Size = new Vector2((float)element.ActualWidth, (float)element.ActualHeight);
ElementCompositionPreview.SetElementChildVisual(element, sprite);
return sprite;
}
@harrypotter06
Copy link

harrypotter06 commented Nov 10, 2017

This Code isn't work.

This errormessages, we get.

Fehler CS1503 Argument "1": Konvertierung von "System.Drawing.Size" in "Windows.Foundation.Size" nicht möglich.
Fehler CS0246 Der Typ- oder Namespacename "Rect" wurde nicht gefunden (möglicherweise fehlt eine using-Direktive oder ein Assemblyverweis).
Fehler CS0246 Der Typ- oder Namespacename "CanvasCreateResourcesEventArgs" wurde nicht gefunden (möglicherweise fehlt eine using-Direktive oder ein Assemblyverweis).
Fehler CS0123 Keine Überladung für "Canvas_CreateResources" stimmt mit dem Delegaten "TypedEventHandler<CanvasControl, CanvasCreateResourcesEventArgs>" überein.
Fehler CS0029 Der Typ "Windows.Foundation.Size" kann nicht implizit in "System.Drawing.Size" konvertiert werden.
Fehler CS1503 Argument "1": Konvertierung von "System.Drawing.Size" in "Windows.Foundation.Size" nicht möglich.

My Usings:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI.Composition;
using Microsoft.Graphics.Canvas.Effects;
using Windows.UI.Xaml.Hosting;
using System.Threading;
using System.Drawing;
using System.Numerics;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Composition;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI;
using Windows.Graphics.DirectX;

@Sergio0694
Copy link
Author

@harrypotter06 sorry for the late reply, didn't get the notification.
The code itself is working fine, the error you get is because you have an incorrect using directive. As the error message says, you're using the System.Drawing.Size object instead of the expected Windows.Foundation.Size object.
You can find the updated version of that code in the library repo (here), including all the list of necessary using directives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment