Blur effects are getting pretty common in app designs, especially since iOS 7 started introducing them in some OS components like Control Center – and even the Windows 10 Start Menu uses the same effect to display a “frosted glass” look over the current background window.
Instead of a simple blur, which has been already explained by the awesome Deani Hansen, we are going to implement an iris blur (as Photoshop calls it; other resources call it “blur around borders”, and Instagram names it “tilt shift” with a radial blur in an UWP app by using the awesome Win2D library. It consists of a circular or ellipsoid area where the image shows focused, but everything else around it is defocused (or “blurred”) progressively, to give it a nice fading effect with no solid transition. So, let’s get started!
Approach
To achieve the result shown above, we are going to draw a blurred version of the image (in our case, a very beautiful one taken from Windows 10’s Spotlight backgrounds) over the original one – however, it will have a transparency mask applied so it can show a “hole” where there is no blurring. For additional artistic result, the hole won’t have clearly defined borders – instead we are going to use a radial gradient so it blends progressively over the non-blurred image. The following image describes how the blurred image with mask applied will be obtained:
We could store the resulting image in a CanvasRenderTarget
instance and then draw it to our CanvasControl
when needed – however, we can skip this intermediate step if we directly draw the original image and then the blurred one on top, just like the followind diagram shows:
With all concepts clear, let’s go ahead with the implementation – I promise you it’s going to be really easy!
Implementation
Start by adding the Win2D.UWP library to your project from NuGet, and creating a CanvasControl
in XAML. Hook up its CreateResources
event and instantiate a new member variable of type CanvasRadialGradientBrush
– along with loading the image we are going to draw. Our radial brush will have Colors.Transparent
as a starting colour and Colors.Black
as the ending one – the ending one could have been Colors.White
or any other solid one though, as the layer will only take the alpha (transparency) component into account.
[code lang=”csharp”]
private void Canvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
// Create instance of radial gradient brush; the center will be transparent and the extremes opaque black.
radialBrush = new CanvasRadialGradientBrush(sender, Colors.Transparent, Colors.Black);
// Load image to draw.
args.TrackAsyncAction(Task.Run(async () =>
{
image = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///SpotlightImage.png"));
}).AsAsyncAction());
}
[/code]
Next, we are going to wire up the Draw
event – and perform all of our drawing there. Start by clearing the CanvasDrawingSession
’s background to a colour of your choice, and then set the Center
, RadiusX
and RadiusY
parameters of the radial gradient brush to meaningful values (in our case the center is always the center of the image/canvas, and the radius will be updated through a Slider
control); then, draw the untouched image through CanvasDrawingSession.DrawImage
:
[code lang=”csharp”]
// Start drawing session and clear background to white.
var session = args.DrawingSession;
args.DrawingSession.Clear(Colors.White);
// Set the center of the radial gradient as the center of the image.
radialBrush.Center = new System.Numerics.Vector2((float)(image.Size.Width / 2.0f), (float)(image.Size.Height / 2.0f));
// Assing gradient radius from slider control.
radialBrush.RadiusX = radialBrush.RadiusY = (float)BlurRadius.Value;
// Draw unaltered image first.
session.DrawImage(image, image.Bounds);
[/code]
Now comes the fun part – working with layers. The logic behind is that you create a layer by calling CanvasDrawingSession.CreateLayer
and passing an opacity value, ICanvasBrush
or CanvasGeometry
and everything that is drawn from that point is affected by the opacity/mask/clipping area – pretty convenient, huh? Then, to stop using it, you just have to Dispose
the CanvasActiveLayer
– or instead, wrap everything inside an using
block.
So, we are going to pass the CanvasRadialGradientBrush
as the mask parameter, and then draw the image again but after applying a GaussianBlurEffect
on it. This way, the masked, blurred version of the image will draw on top of the solid one, composing the desided effect:
[code lang=”csharp”]
// Create a layer, this way all elements drawn will be affected by a transparent mask
// which in our case is the radial gradient.
using (session.CreateLayer(radialBrush))
{
// Create gaussian blur effect.
using (var blurEffect = new GaussianBlurEffect())
{
// Set image to blur.
blurEffect.Source = image;
// Set blur amount from slider control.
blurEffect.BlurAmount = (float)BlurAmount.Value;
// Explicitly set optimization mode to highest quality, since we are using big blur amount values.
blurEffect.Optimization = EffectOptimization.Quality;
// This prevents the blur effect from wrapping around.
blurEffect.BorderMode = EffectBorderMode.Hard;
// Draw blurred image on top of the unaltered one. It will be masked by the radial gradient
// thus showing a transparent hole in the middle, and properly overlaying the alpha values.
session.DrawImage(blurEffect, 0, 0);
}
}
[/code]
I told you it was easy, wasn’t it? And just for reference, here is a video of how the effect looks in the sample project that complements this tutorial:
Source code
Get the source code for the entire sample project in the Win2D-Samples GitHub repository, under the IrisBlurWin2D subfolder.