feat(blur): universal — the blur covers every layer, webcam included

The creator found the use case that reframes the feature: a moment of privacy while the webcam is
on. A blur that can only cover the desktop cannot do that.

- SceneCompositor applies the blur group AFTER every element is composited (still before the
  flash / social bar / ticker / zoom overlays), so it covers webcam, chat, alerts — anything. Its
  position in the layer list no longer affects the output, so it is added at the top and the
  reorder clamp is gone.
- Supersedes the earlier "blur sits below the brand" rule: a universal blur does obscure the logo
  when it covers it. Called out in frame-pipeline.md so it is not re-litigated.
- Preview mirrors it exactly: the blur host moved OUT of CanvasGrid to a sibling above it, so the
  VisualBrush samples every layer (and cannot sample itself). It used to sample only the backdrop,
  which is why a circle over the webcam would have shown sharp pixels in the editor and blurred
  ones in the stream.
This commit is contained in:
2026-11-01 13:29:15 -08:00
parent bb80ec8370
commit bb51164f2f
5 changed files with 29 additions and 73 deletions
+6 -9
View File
@@ -20,6 +20,11 @@
PreviewMouseRightButtonUp="Preview_MouseRightButtonUp">
<Viewbox Stretch="Uniform">
<Canvas x:Name="PreviewCanvas" Width="1920" Height="1080" ClipToBounds="True">
<!-- Privacy blur preview (2026-11-01): the blur is UNIVERSAL — it covers
every layer beneath it, so this host sits ABOVE the whole CanvasGrid
rather than inside it. Each child samples CanvasGrid (all layers, no
recursion because this canvas is a sibling) at the blur's rect and
carries a BlurEffect at the output's strength. -->
<Grid x:Name="CanvasGrid" Width="1920" Height="1080" ClipToBounds="True" Background="Transparent">
<Grid.RenderTransform>
<TransformGroup>
@@ -71,15 +76,6 @@
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">
@@ -474,6 +470,7 @@
<VisualBrush x:Name="ZoomVisualBrush" ViewboxUnits="Absolute" Stretch="Fill"/>
</Grid.Background>
</Grid>
<Canvas x:Name="BlurPreviewHost" Width="1920" Height="1080" IsHitTestVisible="False"/>
</Canvas>
</Viewbox>
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,8,0,0"
+6 -6
View File
@@ -740,11 +740,11 @@ public partial class PreviewPane : UserControl
}
// ─── 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.
// The editor has to show the blur it is about to bake into the output, and the blur is
// UNIVERSAL: it covers every layer, webcam and chat included. Each blur becomes a
// rectangle/ellipse filled with a VisualBrush of the whole CanvasGrid sampled at that rect,
// with a BlurEffect at the same strength the compositor uses. The host canvas is a SIBLING
// above CanvasGrid, which is what stops the brush from sampling itself.
private MainViewModel? _blurVm;
private Scene? _blurScene;
@@ -811,7 +811,7 @@ public partial class PreviewPane : UserControl
{
Width = blur.Width,
Height = blur.Height,
Fill = new VisualBrush(BackdropLayer)
Fill = new VisualBrush(CanvasGrid)
{
Viewbox = new Rect(blur.X, blur.Y, blur.Width, blur.Height),
ViewboxUnits = BrushMappingMode.Absolute,