[Request] Color Bloom to Pictures
Closed this issue · 4 comments
Hi,
is it possible to adapt the Color Bloom
Transition Animation in such a way, that instead of radial expanding a single color one can radial expand an image the same way?
The idea behind is, that I have a Hamburger Button
on my page. When I click it, I want to create a temporal image of the background grid, apply a gaussian blur
-filter on it and then use this image to be radial expanded into the Sidebar Panel View
.
Currently I have only a blurred Sidebar (done in the steps above), that has a Transition in x-direction and it already looks amazing. The radial expansion would definitely give it a more special touch.
To better understand your feedback, I'd like to confirm that I understand your desired experience properly. From what I understand, what you would like to be able to achieve is the following flow:
- User clicks on the hamburger button on the corner of your page.
- After the click, what the user sees is that a blurred version of the background radiates (radial animation) from the hamburger menu button.
The way you are attempting to achieve this is that you have a static picture of the background, which you blur and want to reveal radially. The gradual revealing of the picture makes it look like a blur effect is happening to the actual background.
Are these assumptions correct?
@msftdavid this is correct. This is exactly what I'm trying to achieve.
The blur I currently do the following way:
private async void PrjTestBlur_SizeChanged(object sender, SizeChangedEventArgs e)
{
// fix height and width of blurImage
blurImage.Width = e.NewSize.Width;
blurImage.Height = e.NewSize.Height;
if (blurImage.Visibility == Visibility.Visible || blurImage.Source == null)
{
using (var stream = await mainGrid.RenderToRandomAccessStream())
{
var img = new BitmapImage();
await img.SetSourceAsync(stream);
blurImage.Source = img;
}
SpriteVisual blurVisual = (SpriteVisual)ElementCompositionPreview.GetElementChildVisual(blurImage);
if (blurVisual != null)
{
blurVisual.Size = e.NewSize.ToVector2();
}
}
}
The RenderToRandomAccessStream
function I got from here
and calling the blur with
private async void backgroundBlur(bool show = true)
{
// show blurred image
blurImage.Visibility = Visibility.Visible;
// failsafe
if (_compositor == null)
{
_compositor = ElementCompositionPreview.GetElementVisual(blurImage).Compositor;
// Create a chained effect graph using a BlendEffect, blending color and blur
GaussianBlurEffect blurEffect = new GaussianBlurEffect()
{
Name = "Blur",
BlurAmount = show ? 0f : 9f,
BorderMode = EffectBorderMode.Hard,
Optimization = EffectOptimization.Balanced,
Source = new CompositionEffectSourceParameter("Backdrop")
};
var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect, new[] { "Blur.BlurAmount" });
// Create EffectBrush, BackdropBrush and SpriteVisual
_brush = blurEffectFactory.CreateBrush();
}
// show animation
StartBlurAnimation(show ? 9.0f : 0f);
var destinationBrush = _compositor.CreateBackdropBrush();
_brush.SetSourceParameter("Backdrop", destinationBrush);
var blurSprite = _compositor.CreateSpriteVisual();
blurSprite.Size = new Vector2((float)blurImage.Width, (float)blurImage.Height);
blurSprite.Brush = _brush;
ElementCompositionPreview.SetElementChildVisual(blurImage, blurSprite);
if (!show)
{
await Task.Delay(1500);
blurImage.Visibility = Visibility.Collapsed;
}
}
private void StartBlurAnimation(float blur)
{
ScalarKeyFrameAnimation blurAnimation = _compositor.CreateScalarKeyFrameAnimation();
blurAnimation.InsertKeyFrame(1f, blur);
blurAnimation.Duration = TimeSpan.FromSeconds(2);
_brush.StartAnimation("Blur.BlurAmount", blurAnimation);
}
With the SizeChanged
I get an updated version of the background, so the blur always matches the background content. It could be optimized by only updating, when a blurred content is shown. The function backgroundBlur
here is used to blur the whole screen, but when the Image
blurImage
is set to the SplitView
- element (Hamburgermenu
) it would have the desired effect without the radial animation. Global variables for that page are CompositionEffectBrush _brush;
and Compositor _compositor;
.
Are you expecting a behavior like this?
If yes, then you can achieve this by using CompositionProToolkit
I have created a UserControl called ImageBloomControl, which you can put in each of your pages ( corresponding to each hamburger item). You can set the following properties
- ImageSource - Uri of the image to be displayed
- BlurAmount - Blur radius of the Gaussian blur to be applied on the image.
- Duration - Duration of the bloom animation.
- OffsetX - X Offset of the center of the bloom animation.
- OffsetY - Y Offset of the center of the bloom animation.
When each of your page loads, you can call the Bloom() method of the ImageBloomControl to start the bloom animation.
Note: Don't forget to call the Dispose() method of the ImageBloomControl in the Page's Unloaded event to dispose the objects safely.
Here is the code for ImageBloomControl
ImageBloomControl.xaml
<UserControl
x:Class="TestSurfaceImage.ImageBloomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xaml="using:Microsoft.Graphics.Canvas.UI.Xaml"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Grid x:Name="CompositionGrid"></Grid>
<xaml:CanvasAnimatedControl x:Name="CanvasCtrl"
Draw="OnDrawCanvas"></xaml:CanvasAnimatedControl>
</Grid>
</UserControl>
ImageBloomControl.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
using CompositionProToolkit;
using CompositionProToolkit.Expressions;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.UI.Xaml;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace TestSurfaceImage
{
public sealed partial class ImageBloomControl : UserControl, IDisposable
{
#region Fields
private Compositor _compositor;
private ICompositionGenerator _generator;
private IMaskSurface _opacityMaskSurface;
private LayerVisual _layer;
private float _radiusFrom;
private float _radiusTo;
private float _currentRadius;
private bool _bloomInProgress;
private float _maxFrames;
private float _currFrame;
private Vector2 _bloomCenter;
#endregion
#region Dependency Properties
#region ImageSource
/// <summary>
/// ImageSource Dependency Property
/// </summary>
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(Uri), typeof(ImageBloomControl),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets the ImageSource property. This dependency property
/// indicates the Uri of the image to be displayed.
/// </summary>
public Uri ImageSource
{
get { return (Uri)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
#endregion
#region BlurAmount
/// <summary>
/// BlurAmount Dependency Property
/// </summary>
public static readonly DependencyProperty BlurAmountProperty =
DependencyProperty.Register("BlurAmount", typeof(double), typeof(ImageBloomControl),
new PropertyMetadata(10));
/// <summary>
/// Gets or sets the BlurAmount property. This dependency property
/// indicates the blur radius of the backdrop brush.
/// </summary>
public double BlurAmount
{
get { return (double)GetValue(BlurAmountProperty); }
set { SetValue(BlurAmountProperty, value); }
}
#endregion
#region Duration
/// <summary>
/// Duration Dependency Property
/// </summary>
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(ImageBloomControl),
new PropertyMetadata(TimeSpan.FromSeconds(1)));
/// <summary>
/// Gets or sets the Duration property. This dependency property
/// indicates the duration of the bloom animation.
/// </summary>
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
#endregion
#region OffsetX
/// <summary>
/// OffsetX Dependency Property
/// </summary>
public static readonly DependencyProperty OffsetXProperty =
DependencyProperty.Register("OffsetX", typeof(double), typeof(ImageBloomControl),
new PropertyMetadata(0));
/// <summary>
/// Gets or sets the OffsetX property. This dependency property
/// indicates the offset of the center of the bloom on the X-axis.
/// </summary>
public double OffsetX
{
get { return (double)GetValue(OffsetXProperty); }
set { SetValue(OffsetXProperty, value); }
}
#endregion
#region OffsetY
/// <summary>
/// OffsetY Dependency Property
/// </summary>
public static readonly DependencyProperty OffsetYProperty =
DependencyProperty.Register("OffsetY", typeof(double), typeof(ImageBloomControl),
new PropertyMetadata(0));
/// <summary>
/// Gets or sets the OffsetY property. This dependency property
/// indicates the offset of the center of the bloom on the Y-axis.
/// </summary>
public double OffsetY
{
get { return (double)GetValue(OffsetYProperty); }
set { SetValue(OffsetYProperty, value); }
}
#endregion
#endregion
#region Construction / Initialization
public ImageBloomControl()
{
InitializeComponent();
Loaded += OnLoaded;
}
#endregion
#region APIs
public void Bloom()
{
if (_bloomInProgress)
return;
var x = OffsetX.Single();
var y = OffsetY.Single();
_bloomCenter = new Vector2(x, y);
var circleGeometry = CanvasGeometry.CreateCircle(_generator.Device, _bloomCenter, 0);
_opacityMaskSurface.Redraw(circleGeometry);
_currentRadius = 0;
_radiusFrom = 0;
// Calculate the max radius
// Max radius will be the largest distance from the bloom center
// to the corners of the CompositionGrid
var distances = new List<float>();
distances.Add(Vector2.Distance(_bloomCenter, new Vector2(0, 0)));
distances.Add(Vector2.Distance(_bloomCenter, new Vector2(CompositionGrid.ActualWidth.Single(), 0)));
distances.Add(Vector2.Distance(_bloomCenter, new Vector2(0, CompositionGrid.ActualHeight.Single())));
distances.Add(Vector2.Distance(_bloomCenter, new Vector2(CompositionGrid.ActualWidth.Single(), CompositionGrid.ActualHeight.Single())));
_radiusTo = distances.Max();
// Calculate the number of frames required to animate
var duration = Duration;
_maxFrames = (float)Math.Floor((float)duration.TotalMilliseconds / (float)CanvasCtrl.TargetElapsedTime.TotalMilliseconds);
_currFrame = 0f;
_bloomInProgress = true;
}
public void Hide()
{
_bloomInProgress = false;
var circleGeometry = CanvasGeometry.CreateCircle(_generator.Device, _bloomCenter, 0);
_opacityMaskSurface.Redraw(circleGeometry);
_currFrame = 0;
}
public void Dispose()
{
var visual = _layer.Children.ElementAt(0) as SpriteVisual;
visual?.Dispose();
_layer.Dispose();
_layer = null;
_opacityMaskSurface.Dispose();
_opacityMaskSurface = null;
_generator.Dispose();
_generator = null;
_compositor = null;
}
#endregion
#region Event Handlers
private async void OnLoaded(Object sender, RoutedEventArgs e)
{
if (_compositor == null)
{
_compositor = ElementCompositionPreview.GetElementVisual(CompositionGrid).Compositor;
_generator = CompositionGeneratorFactory.GetCompositionGenerator(_compositor);
_layer = _compositor.CreateLayerVisual();
_layer.Size = new Vector2(CompositionGrid.ActualWidth.Single(), CompositionGrid.ActualHeight.Single());
// Create the effect to create the opacity mask
var opacityMaskEffect = new CompositeEffect
{
// CanvasComposite.DestinationIn - Intersection of source and mask.
// Equation: O = MA * S
// where O - Output pixel, MA - Mask Alpha, S - Source pixel.
Mode = CanvasComposite.DestinationIn,
Sources =
{
new CompositionEffectSourceParameter("source"),
new CompositionEffectSourceParameter("mask")
}
};
var opacityMaskEffectFactory = _compositor.CreateEffectFactory(opacityMaskEffect);
var opacityMaskBrush = opacityMaskEffectFactory.CreateBrush();
// The mask for the layer
var circleGeometry = CanvasGeometry.CreateCircle(_generator.Device, Vector2.Zero, 0);
_opacityMaskSurface = _generator.CreateMaskSurface(_layer.Size.ToSize(), circleGeometry);
opacityMaskBrush.SetSourceParameter("mask", _compositor.CreateSurfaceBrush(_opacityMaskSurface.Surface));
// Apply the opacity mask brush to the layer
_layer.Effect = opacityMaskBrush;
// Create the blur effect
var blurAmount = BlurAmount;
var blurEffect = new GaussianBlurEffect
{
Name = "Blur",
BlurAmount = blurAmount.Single(),
BorderMode = EffectBorderMode.Hard,
Source = new CompositionEffectSourceParameter("image")
};
var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect, () => blurEffect.BlurAmount);
var blurBrush = blurEffectFactory.CreateBrush();
// Load the image
var uri = ImageSource;
var imageSurface =
await _generator.CreateImageSurfaceAsync(uri, _layer.Size.ToSize(), ImageSurfaceOptions.Default);
blurBrush.SetSourceParameter("image", _compositor.CreateSurfaceBrush(imageSurface.Surface));
var visual = _compositor.CreateSpriteVisual();
visual.Size = _layer.Size;
visual.Brush = blurBrush;
_layer.Children.InsertAtTop(visual);
ElementCompositionPreview.SetElementChildVisual(CompositionGrid, _layer);
}
}
private void OnDrawCanvas(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
if (!_bloomInProgress)
return;
if (_currFrame > _maxFrames)
{
_bloomInProgress = false;
return;
}
// ================================================================================================
// Cubic Ease Out
// ================================================================================================
var t = (++_currFrame) / _maxFrames;
var ts = t * t;
var tc = ts * t;
// Cubic ease out fn => b + c * (tc - (3 * ts) + (3 * t))
_currentRadius = _radiusFrom + (_radiusTo - _radiusFrom) * (tc - (3 * ts) + (3 * t));
// ================================================================================================
var circleGeometry = CanvasGeometry.CreateCircle(_generator.Device, _bloomCenter, _currentRadius);
_opacityMaskSurface.Redraw(circleGeometry);
}
#endregion
}
}
Since Composition APIs cannot be used to animate non-Composition objects, I am using my own animation logic which is invoked periodically using CanvasAnimatedControl's Draw method.
Usage
<local:ImageBloomControl x:Name="BloomControl"
ImageSource="ms-appx:///Assets/Images/Image3.jpg"
BlurAmount="4"
Duration="00:00:02"
OffsetX="0"
OffsetY="100"></local:ImageBloomControl>
@ratishphilip wow! this is exactly what I was looking for. And then with a full code example - couldn't hope for more. Thank you very very much.
Also I have never heard from CompositionProToolkit, it seams to have quite some interesting functions.