Reputation: 15253
In WPF there is an Effect
property of most controls such as Grid.Effect
which allows creating a blurred grid. I wanted to see how can I create a popup with a blurred background in WinRT?
Upvotes: 2
Views: 1034
Reputation: 31724
An alternative to not doing it or using DirectX might be to use the new RenderTargetBitmap
class with the RenderAsync()
method that allows you to get access to the pixels of the control from C# or VB level (assuming that is what you are using), which might be easier than DirectX and actually fast enough for some basic effects to run on the CPU. I've done it in the FxContentControl
in the WinRT XAML Toolkit last weekend and it seems to work OK in some simple scenarios with the main problem being - when to update the effect.
The control currently doesn't blur anything, but rather finds the expanded pixels of the content and makes them black, serving as a poor man's way to generate strokes for a TextBlock
or for any other control with some transparent and some non-transparent areas. You could change it to blur or add an option to do it on demand fairly easily.
Here's the default style of the templated control:
<Style
TargetType="controls:FxContentControl">
<Setter
Property="HorizontalContentAlignment"
Value="Left" />
<Setter
Property="VerticalContentAlignment"
Value="Top" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="controls:FxContentControl">
<Grid>
<Image
x:Name="BackgroundFxImage"
Stretch="None"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Grid
x:Name="RenderedGrid"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter
x:Name="ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Content="{TemplateBinding Content}" />
</Grid>
<Image
x:Name="ForegroundFxImage"
Stretch="None"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the code:
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using WinRTXamlToolkit.Imaging;
namespace WinRTXamlToolkit.Controls
{
public class FxContentControl : ContentControl
{
private Image _backgroundFxImage;
private Image _foregroundFxImage;
private ContentPresenter _contentPresenter;
private Grid _renderedGrid;
public FxContentControl()
{
this.DefaultStyleKey = typeof(FxContentControl);
}
protected override async void OnApplyTemplate()
{
base.OnApplyTemplate();
_backgroundFxImage = this.GetTemplateChild("BackgroundFxImage") as Image;
_foregroundFxImage = this.GetTemplateChild("ForegroundFxImage") as Image;
_contentPresenter = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
_renderedGrid = this.GetTemplateChild("RenderedGrid") as Grid;
if (_renderedGrid != null)
{
_renderedGrid.SizeChanged += this.OnContentPresenterSizeChanged;
}
if (_renderedGrid.ActualHeight > 0)
{
await this.UpdateFx();
}
}
private async void OnContentPresenterSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
await this.UpdateFx();
}
private async Task UpdateFx()
{
await this.UpdateBackgroundFx();
}
private async Task UpdateBackgroundFx()
{
if (_renderedGrid.ActualHeight < 1 ||
_backgroundFxImage == null)
{
return;
}
var rtb = new RenderTargetBitmap();
await rtb.RenderAsync(_renderedGrid);
var pw = rtb.PixelWidth;
var ph = rtb.PixelHeight;
var wb = _backgroundFxImage.Source as WriteableBitmap;
if (wb == null ||
wb.PixelWidth != pw ||
wb.PixelHeight != ph)
{
wb = new WriteableBitmap(pw, ph);
}
await ProcessContentImage(rtb, wb, pw, ph);
_backgroundFxImage.Source = wb;
}
protected virtual async Task ProcessContentImage(RenderTargetBitmap rtb, WriteableBitmap wb, int pw, int ph)
{
var rtbBuffer = await rtb.GetPixelsAsync();
var rtbPixels = rtbBuffer.GetPixels();
var wbBuffer = wb.PixelBuffer;
var wbPixels = wbBuffer.GetPixels();
// Expand
int expansion = 1;
for (int x = 0; x < pw; x++)
for (int y = 0; y < ph; y++)
{
int x1min = Math.Max(0, x - expansion);
int x1max = Math.Min(x + expansion, pw - 1);
int y1min = Math.Max(0, y - expansion);
int y1max = Math.Min(y + expansion, ph - 1);
//bool found = false;
byte maxa = 0;
for (int x1 = x1min; x1 <= x1max; x1++)
for (int y1 = y1min; y1 <= y1max; y1++)
{
var a = rtbPixels.Bytes[4 * (y1 * pw + x1) + 3];
if (a > maxa)
maxa = a;
}
wbPixels.Bytes[4 * (y * pw + x)] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 1] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 2] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 3] = maxa;
}
wbPixels.UpdateFromBytes();
}
}
}
It also uses the following IBuffer wrapper:
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage.Streams;
namespace WinRTXamlToolkit.Imaging
{
// ReSharper disable InconsistentNaming - This class extends IBuffer
/// <summary>
/// Contains extensions for an IBuffer interface in the context of a WriteableBitmap,
/// which exposes one to access its pixels.
/// </summary>
public static class IBufferExtensions
// ReSharper restore InconsistentNaming
{
/// <summary>
/// Gives access to the pixels of a WriteableBitmap given an IBuffer
/// exposed by Pixels property.
/// </summary>
/// <remarks>
/// Note that creating this object copies the pixels buffer
/// into the Bytes byte array for quick pixel access
/// and the array needs to be copied back to the pixels buffer
/// to update the bitmap with a call to UpdateFromBytes().
/// This is acceptable for convenience and possibly best for
/// performance in some scenarios, but it does add some upfront
/// overhead as well overhead to update the bitmap at the end.
/// This is only a theory and for better performance it might be
/// good to test different approaches.
/// The goal of this approach is code simplicity. For best performance
/// using native code and/or DirectX is recommended.
/// </remarks>
public class PixelBufferInfo
{
private readonly Stream _pixelStream;
/// <summary>
/// The bytes of the pixel stream.
/// </summary>
public byte[] Bytes;
/// <summary>
/// Initializes a new instance of the <see cref="PixelBufferInfo" /> class.
/// </summary>
/// <param name="pixelBuffer">The pixel buffer returned by WriteableBitmap.PixelBuffer.</param>
public PixelBufferInfo(IBuffer pixelBuffer)
{
_pixelStream = pixelBuffer.AsStream();
this.Bytes = new byte[_pixelStream.Length];
_pixelStream.Seek(0, SeekOrigin.Begin);
_pixelStream.Read(this.Bytes, 0, Bytes.Length);
//this.Pixels = bytes.ToPixels();
}
/// <summary>
/// Updates the associated pixel buffer from bytes.
/// </summary>
public void UpdateFromBytes()
{
_pixelStream.Seek(0, SeekOrigin.Begin);
_pixelStream.Write(Bytes, 0, Bytes.Length);
}
}
/// <summary>
/// Gets the pixels access wrapper for a PixelBuffer property of a WriteableBitmap.
/// </summary>
/// <param name="pixelBuffer">The pixel buffer.</param>
/// <returns></returns>
public static PixelBufferInfo GetPixels(this IBuffer pixelBuffer)
{
return new PixelBufferInfo(pixelBuffer);
}
}
}
Upvotes: 2
Reputation: 59763
As you've noticed, Effect
s are not available currently in WinRT.
Ideas:
Effect
s, at least in the Widows Application Store "design language" just aren't a good match.Upvotes: 2
Reputation: 4311
Effects are not supported in WinRT in XAML. You can use DirectX interop and put shaders there using C++ (SwapChainPanel).
Upvotes: 2