feat(blur): the editor shows the blur — whatever is beneath it is blurred, in the preview
The preview canvas is WPF visuals; a blur layer contributes no visual of its own, so until now the creator saw nothing where the blur was and could not tell what it was hiding. Each visible blur now draws a rectangle (or ellipse) filled with a VisualBrush of the whole BackdropLayer — the desktop capture plus the background art — sampled at the blur's own rect and carrying a BlurEffect at the same strength the compositor uses. The host canvas sits after the backdrop and before the element layers, which is the model's own z-rule, so the preview shows exactly what the output will: desktop blurred, brand and everything above it untouched. Rebuilt on scene change, on add/remove, and on any blur property change (position, size, shape, strength) — the brush samples the live backdrop, so the picture keeps moving underneath.
This commit is contained in:
@@ -71,6 +71,15 @@
|
||||
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"/>
|
||||
<Image Source="{Binding ActiveBackgroundImage}" Stretch="UniformToFill"/>
|
||||
</Grid>
|
||||
<!-- Privacy blur preview (2026-11-01): whatever is BENEATH a blur layer
|
||||
is blurred, right here in the editor — not a hatched placeholder. Each
|
||||
child is a rectangle/ellipse filled with a VisualBrush of the whole
|
||||
BackdropLayer (the desktop + background art), clipped to the blur's
|
||||
shape and blurred at the same strength the output uses. Hosted AFTER
|
||||
the backdrop and BEFORE the element layers, which is the model's own
|
||||
z-rule: the blur sits above the desktop and below the brand. -->
|
||||
<Canvas x:Name="BlurPreviewHost" Width="1920" Height="1080" IsHitTestVisible="False"/>
|
||||
|
||||
<Image x:Name="TransitionOverlay" Width="1920" Height="1080" Stretch="Fill"
|
||||
IsHitTestVisible="False" Visibility="Collapsed"/>
|
||||
<Canvas x:Name="OverlayCanvas" Width="1920" Height="1080">
|
||||
|
||||
@@ -47,6 +47,7 @@ public partial class PreviewPane : UserControl
|
||||
newVm.PreviewTransition += PlayPreviewTransition;
|
||||
UpdateZoomBox();
|
||||
UpdateSelectedLayerZOrder();
|
||||
HookBlurPreview();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,4 +738,95 @@ public partial class PreviewPane : UserControl
|
||||
if (DataContext is MainViewModel vm) vm.ToggleSettingsDrawer();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// ─── Privacy blur preview (2026-11-01) ───
|
||||
// The editor has to show the blur it is about to bake into the output: "whatever is beneath the
|
||||
// blur layer is blurred". Each blur becomes a rectangle/ellipse filled with a VisualBrush of the
|
||||
// whole BackdropLayer (desktop + background art) sampled at that rect, with a BlurEffect at the
|
||||
// same strength the compositor uses. The host canvas sits between the backdrop and the element
|
||||
// layers, which is the model's own z-rule.
|
||||
private MainViewModel? _blurVm;
|
||||
private Scene? _blurScene;
|
||||
|
||||
private void HookBlurPreview()
|
||||
{
|
||||
if (_blurVm != null) _blurVm.PropertyChanged -= BlurViewModelChanged;
|
||||
_blurVm = DataContext as MainViewModel;
|
||||
if (_blurVm != null) _blurVm.PropertyChanged += BlurViewModelChanged;
|
||||
HookBlurScene();
|
||||
}
|
||||
|
||||
private void BlurViewModelChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(MainViewModel.StagedScene)) HookBlurScene();
|
||||
else if (e.PropertyName is nameof(MainViewModel.ActiveBackgroundImage)
|
||||
or nameof(MainViewModel.BackgroundImage)) RebuildBlurPreview();
|
||||
}
|
||||
|
||||
private void HookBlurScene()
|
||||
{
|
||||
if (_blurScene != null)
|
||||
{
|
||||
_blurScene.Elements.CollectionChanged -= BlurElementsChanged;
|
||||
foreach (var element in _blurScene.Elements) element.PropertyChanged -= BlurElementChanged;
|
||||
}
|
||||
|
||||
_blurScene = _blurVm?.StagedScene;
|
||||
|
||||
if (_blurScene != null)
|
||||
{
|
||||
_blurScene.Elements.CollectionChanged += BlurElementsChanged;
|
||||
foreach (var element in _blurScene.Elements) element.PropertyChanged += BlurElementChanged;
|
||||
}
|
||||
|
||||
RebuildBlurPreview();
|
||||
}
|
||||
|
||||
private void BlurElementsChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
foreach (var element in e.OldItems?.OfType<SceneElement>() ?? System.Linq.Enumerable.Empty<SceneElement>())
|
||||
element.PropertyChanged -= BlurElementChanged;
|
||||
foreach (var element in e.NewItems?.OfType<SceneElement>() ?? System.Linq.Enumerable.Empty<SceneElement>())
|
||||
element.PropertyChanged += BlurElementChanged;
|
||||
RebuildBlurPreview();
|
||||
}
|
||||
|
||||
private void BlurElementChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is Source { IsBlur: true }) RebuildBlurPreview();
|
||||
}
|
||||
|
||||
/// <summary>Rebuild one overlay shape per visible blur. Cheap by design: a handful of shapes,
|
||||
/// and the VisualBrush samples the live backdrop so the preview keeps moving underneath.</summary>
|
||||
private void RebuildBlurPreview()
|
||||
{
|
||||
if (BlurPreviewHost == null) return;
|
||||
BlurPreviewHost.Children.Clear();
|
||||
if (_blurScene == null) return;
|
||||
|
||||
foreach (var blur in _blurScene.Elements.OfType<Source>()
|
||||
.Where(s => s.IsBlur && s.IsVisible && s.Width > 0 && s.Height > 0))
|
||||
{
|
||||
var shape = new Rectangle
|
||||
{
|
||||
Width = blur.Width,
|
||||
Height = blur.Height,
|
||||
Fill = new VisualBrush(BackdropLayer)
|
||||
{
|
||||
Viewbox = new Rect(blur.X, blur.Y, blur.Width, blur.Height),
|
||||
ViewboxUnits = BrushMappingMode.Absolute,
|
||||
Stretch = Stretch.Fill,
|
||||
},
|
||||
// 4..24 px at master scale, the same shape of curve the compositor's radius uses.
|
||||
Effect = new BlurEffect { Radius = 4 + 20 * Math.Clamp(blur.BlurAmount, 0, 1), KernelType = KernelType.Gaussian },
|
||||
};
|
||||
|
||||
if (blur.ClipShape == ClipShape.Round)
|
||||
shape.Clip = new EllipseGeometry(new Rect(0, 0, blur.Width, blur.Height));
|
||||
|
||||
Canvas.SetLeft(shape, blur.X);
|
||||
Canvas.SetTop(shape, blur.Y);
|
||||
BlurPreviewHost.Children.Add(shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user