TASK 51 slice 5: webcam Background section in the Layers properties — Remove/Blur PillToggles (radio-exclusive) + Strength slider; strength changes never blank the webcam (new variant inherits the same-effect frame + bitmap). TASK 51 built (Unit: bg-effects UI)
This commit is contained in:
@@ -284,6 +284,37 @@
|
||||
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Foreground="#a0a0b0" FontSize="11"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- TASK 51: background effects — two pills acting as radio buttons
|
||||
(one on turns the other off; both off = no effect). -->
|
||||
<TextBlock Text="Background" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,6,0,6"/>
|
||||
<DockPanel Margin="0,0,0,4">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Remove" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
||||
<ToggleButton x:Name="RemoveBackgroundPill" Style="{StaticResource PillToggle}"
|
||||
IsChecked="{Binding SelectedElement.RemoveBackground, Mode=TwoWay}"
|
||||
AutomationProperties.Name="Remove background"
|
||||
ToolTip="Remove the background behind you — the layers below show through"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,0,0,4">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Blur" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
||||
<ToggleButton x:Name="BlurBackgroundPill" Style="{StaticResource PillToggle}"
|
||||
IsChecked="{Binding SelectedElement.BlurBackground, Mode=TwoWay}"
|
||||
AutomationProperties.Name="Blur background"
|
||||
ToolTip="Blur the background behind you"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,0,0,6"
|
||||
Visibility="{Binding SelectedElement.HasBackgroundEffect, Converter={StaticResource BoolToVis}}">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Strength" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
||||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.BackgroundStrength, StringFormat={}{0:P0}}"
|
||||
Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="34"/>
|
||||
<Slider x:Name="BackgroundStrengthSlider" Minimum="0" Maximum="1"
|
||||
Value="{Binding SelectedElement.BackgroundStrength, Mode=TwoWay}"
|
||||
TickFrequency="0.05" IsSnapToTickEnabled="True" VerticalAlignment="Center"
|
||||
AutomationProperties.Name="Background effect strength"
|
||||
ToolTip="Blur: how strong the blur is · Remove: how soft the edge is"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Countdown config -->
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ See [`schema.md`](../schema.md) for the memory-map conventions.
|
||||
| `TopBar.xaml(.cs)` | Row 0 session surface (2026-09-22 redesign): brand + gear menu (left), one segmented REC|ON-AIR mode switch + world actions (Record: Start Recording · Stream: Go Live + Test) + reality line while running (dot/word/elapsed) + End (center), account zone — Sign In or avatar w/ Change Account/Logout context menu (right). `RefreshAvatar()`, `GearButton_Click` |
|
||||
| `PreviewPane.xaml(.cs)` | Row 1 Col 1 center: the 1920×1080 `OverlayCanvas`/`SelectionOverlay`/`BrandFlashLayer`/`SocialBarElement`, text pull-out drawer, and the live-controls row (TRAX / game / mic meters + sliders + speakers + socials) |
|
||||
| `SceneThumbnailStrip.xaml(.cs)` | Row 0 scene-picker strip (`ItemsControl` over `Scenes`), staged-red / live-green border highlight |
|
||||
| `LeftPanel.xaml(.cs)` | Row 1 Col 0 left panel: edit mode → layers+properties; live → YouTube chat. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept |
|
||||
| `LeftPanel.xaml(.cs)` | Row 1 Col 0 left panel: edit mode → layers+properties; live → YouTube chat. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept. **TASK 51:** the webcam properties carry the Background section — `RemoveBackgroundPill` / `BlurBackgroundPill` (`PillToggle`, radio-exclusive via `WebcamSceneConfig.RemoveBackground`/`BlurBackground`) + `BackgroundStrengthSlider` (0..1, 5% snaps, shown only while an effect is on) |
|
||||
| `OverlayHost.xaml(.cs)` | Root-grid overlay dialogs: Settings / Report Bug / Feature Request / About (+ licenses + license-key entry). `BackToAbout_Click` lives here |
|
||||
| `SceneThumbnailStrip.xaml.cs` | Code-behind for the strip above |
|
||||
| `LeftPanel.xaml.cs` | Code-behind for the panel above |
|
||||
|
||||
+33
-36
@@ -1,50 +1,47 @@
|
||||
# HANDOFF — 2026-10-09 (TASK 51 slice 1 shipped: background-effects engine)
|
||||
# HANDOFF — 2026-10-09 (TASK 51 webcam background Remove + Blur — BUILT)
|
||||
|
||||
## Branch
|
||||
- main (no feature branch). Pushed through `f8dd107`; the TASK 51 slice-1 commit sits on top,
|
||||
**not pushed** (push at the user's call).
|
||||
- main (no feature branch). Pushed through `f8dd107`; everything after is local, **not pushed**
|
||||
(push at the creator's call).
|
||||
|
||||
## Dirty files (NOT mine — do NOT commit or co-edit)
|
||||
## Dirty files (NOT mine — do NOT commit)
|
||||
- The in-flight "pinned chat" work unit: `Models/YouTube.cs`, `ViewModels/MainViewModel.Chat.cs`,
|
||||
`Controls/LeftPanel.xaml`, untracked `Helpers/RelayCommandGeneric.cs`.
|
||||
untracked `Helpers/RelayCommandGeneric.cs`, and **its hunks in `Controls/LeftPanel.xaml`**
|
||||
(lines ~599–610, the chat area). TASK 51 slice 5 edited the SAME file in a different region (the
|
||||
webcam properties, ~line 287); only the TASK 51 hunk was staged (`git apply --cached` of that hunk
|
||||
alone) — the pinned-chat hunks are still unstaged and untouched.
|
||||
- The creator's untracked logo art in `Assets/` (`llamacasty text.*`, `llamacasty-text-nobg*.png`,
|
||||
`thin logo wordmark.png`).
|
||||
|
||||
## What just happened — TASK 51 slice 1 (engine)
|
||||
- **Model:** Google's official MediaPipe Selfie Segmenter (landscape, 144×256, Apache-2.0),
|
||||
converted to ONNX by our own `tools/selfie-seg/convert_tflite_to_onnx.py` and **verified against
|
||||
MediaPipe's own runtime** (`verify_against_mediapipe.py`, max |diff| 5e-5). Embedded in the exe
|
||||
(`Assets/Models/selfie_segmenter_landscape.onnx`, 430 KB, in git).
|
||||
- **Code:** `Services/BackgroundEffects/` — `IPersonSegmenter` seam, `OnnxPersonSegmenter`
|
||||
(onnxruntime 1.31.0 CPU, never throws), `BackgroundEffectProcessor` (None passthrough, Remove =
|
||||
alpha mask, Blur = passthrough until slice 2). `Models/BackgroundEffect.cs` enum.
|
||||
- **Not wired:** nothing calls it yet — no config, UI, or camera hookup. Zero risk to the live path.
|
||||
- **Docs:** ai.md (stale "background removal PERMANENTLY OUT" bullet replaced by the reversal + a
|
||||
"Webcam background effects" section + a model-provenance licensing guard), THIRD-PARTY-NOTICES
|
||||
items 11 (ONNX Runtime, MIT) + 12 (MediaPipe model, Apache-2.0 with §4b change note),
|
||||
TASK 51 file + TASKS.md row, Services/Models indexes, MyMistakes conversion recipe.
|
||||
## What just happened — TASK 51, all five slices
|
||||
1. **Engine** — Google's MediaPipe Selfie Segmenter (landscape, Apache-2.0) converted to ONNX by
|
||||
`tools/selfie-seg/` and verified vs MediaPipe's own runtime; embedded resource; onnxruntime CPU.
|
||||
2. **Blur** — background at ¼ res, 3 box passes, mask-blended; Remove gets edge feather.
|
||||
3. **Config** — `WebcamSceneConfig.BackgroundEffect` + `BackgroundStrength`, persisted (additive cols).
|
||||
4. **Hookup** — `CameraManager` renders per-scene effect **variants** (one segmentation per raw frame
|
||||
on a latest-frame-drop worker; raw path untouched); VM `SyncWebcamEffects()` declares/adopts them.
|
||||
Synthetic 720p Debug: segment 1.6 ms, Remove 3.3 ms, Blur 17.5 ms.
|
||||
5. **UI** — Background section in the webcam's Layers properties: Remove / Blur `PillToggle`s,
|
||||
radio-exclusive, + Strength slider (visible while an effect is on).
|
||||
Also this session: 64-bit requirement (`PlatformTarget=x64`), GPL-3.0-or-later licensing, README
|
||||
rewrite, TASK 55 (disk hygiene) + TASK 56 (secret scan before going public) queued.
|
||||
|
||||
## Tests
|
||||
- Full suite **408 passed / 0 failed**, 0 build warnings. New: `BackgroundEffectEngineTests` (Remove
|
||||
integration test with a fake segmenter; bundled-model contract test vs a MediaPipe reference mask,
|
||||
fixtures in `ytLive.Tests/Fixtures/`).
|
||||
- Full suite **412 passed / 0 failed**, 0 build warnings. New: `BackgroundEffectEngineTests` (Remove,
|
||||
Blur, bundled-model contract), `CameraManagerTests` (variant reaches compositor + preview, raw
|
||||
stays raw), `LayoutStorePersistenceTests` (per-scene roundtrip + legacy migration),
|
||||
`WebcamBackgroundPillTests` (radio semantics + XAML wiring).
|
||||
- Known environment flakes (not regressions): `Mix_HonorsProviderGains…` (fails while system audio
|
||||
plays), `RealMouseDrag…` (needs an idle desktop).
|
||||
|
||||
## Known issues / follow-ups
|
||||
- **64-bit only (2026-10-09):** `PlatformTarget=x64` in both csproj files (verified: ytLive.dll PE
|
||||
machine 0x8664, 408/408 green). TASK 48 item 2 carries the x64-only MSIX + `-r win-x64` publish.
|
||||
- `onnxruntime.dll` is ~17 MB; an RID-less build copies every RID's native folder — TASK 48's MSIX
|
||||
publish must pin win-x64.
|
||||
- **License: GPL-3.0-or-later** (2026-10-09: `LICENSE`, `ADDITIONAL-TERMS.md` with §7 b/c/e, icon +
|
||||
logo all rights reserved, AI-generated backgrounds/alert video = GPL placeholders; both files ship
|
||||
beside the exe). Source offer = `https://llgit.llamachile.tube/gramps/LlamaCasty` (Gitea; the
|
||||
creator toggles it public), in the notices header + README. **Before it goes public: scan git
|
||||
history for secrets.** README rewritten (creator's voice, generic comparison, real build steps).
|
||||
Still stale and NOT yet rewritten: ai.md "Windows packaging" (Store IAP / branding-flash paid
|
||||
delta) and "Monetization" sections, MARCOM.md asset table (`llama-logo.png` doesn't exist).
|
||||
- **Creator's real-camera check (TASK 51):** toggle Remove/Blur on a webcam, watch edges/lighting,
|
||||
and read `startup.log` → "background effects avg … ms/frame".
|
||||
- `onnxruntime.dll` ~17 MB; TASK 48's MSIX publish must pin `-r win-x64` (x64-only already enforced).
|
||||
- Still stale, not rewritten: ai.md "Windows packaging" (Store IAP / branding-flash paid delta) and
|
||||
"Monetization" sections; MARCOM.md asset table (`llama-logo.png` doesn't exist).
|
||||
- TASK 56: secret-scan the full history BEFORE the Gitea repo (`gramps/LlamaCasty`) goes public.
|
||||
- Carried: "Use default background" toggle still not working (from 2026-10-07).
|
||||
|
||||
## Next step
|
||||
- TASK 51 slice 2: **Blur** in `BackgroundEffectProcessor` (separable box/gaussian, strength), one
|
||||
integration test. Then slice 3 config + persistence (`BackgroundEffect` + `Strength`), slice 4
|
||||
camera-path hookup + perf check on the creator's box, slice 5 UI: radio-exclusive Remove/Blur pill
|
||||
switches + strength slider in the webcam's Layers properties.
|
||||
- Creator runs the app: webcam → Layers properties → Background → Remove / Blur + Strength.
|
||||
|
||||
@@ -26,9 +26,13 @@ public class WebcamSceneConfig : SceneElement
|
||||
if (!Set(ref _backgroundEffect, value)) return;
|
||||
Raise(nameof(RemoveBackground));
|
||||
Raise(nameof(BlurBackground));
|
||||
Raise(nameof(HasBackgroundEffect));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>True while Remove or Blur is on — shows the strength slider.</summary>
|
||||
public bool HasBackgroundEffect => BackgroundEffect != BackgroundEffect.None;
|
||||
|
||||
private double _backgroundStrength = 0.5;
|
||||
/// <summary>TASK 51: 0..1 — Blur amount, or Remove's edge softness (persisted).</summary>
|
||||
public double BackgroundStrength
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Plain data types. No logic beyond what a property can carry. See
|
||||
| `ZoomSceneElement.cs` | **Transient zoom box (TASK 52, creator-corrected 2026-10-08):** carries the box rect AND the anchored `CaptureX`/`CaptureY` (the fixed box/2 capture window set on `ActivateZoom`; moving the box never touches it). `IsZoomActive` flips `Kind` Static→Dynamic (leaves the baked base while active); never persisted (LayoutStore save skips non-Source/non-WebcamSceneConfig rows), never shown in the layers list or the element template (`IsZoomLayer` gates both). The compositor SKIPS the active element in every layer loop and draws its 2x content LAST (layer n+1) via `ZoomMagnifier`. |
|
||||
| `Webcam.cs` | Singleton webcam identity: `Id`, `DeviceId`, `Name` (one row app-wide) |
|
||||
| `BackgroundEffect.cs` | **TASK 51:** per-webcam background effect enum `None` / `Remove` / `Blur`, carried by `WebcamSceneConfig` |
|
||||
| `WebcamSceneConfig.cs` | Per-scene webcam placement (subclass of `SceneElement`): geometry + `IsVisible` + border (`BorderColor`/`BorderOpacity`/`BorderWidth`/`BorderAnimation`) + `VideoImageSource`; `WebcamId` links to `Webcam`; **TASK 51:** `BackgroundEffect` + `BackgroundStrength` (0..1, default 0.5) + the radio-pill surface `RemoveBackground`/`BlurBackground` (one on turns the other off; both off = None) |
|
||||
| `WebcamSceneConfig.cs` | Per-scene webcam placement (subclass of `SceneElement`): geometry + `IsVisible` + border (`BorderColor`/`BorderOpacity`/`BorderWidth`/`BorderAnimation`) + `VideoImageSource`; `WebcamId` links to `Webcam`; **TASK 51:** `BackgroundEffect` + `BackgroundStrength` (0..1, default 0.5) + the radio-pill surface `RemoveBackground`/`BlurBackground` (one on turns the other off; both off = None) + `HasBackgroundEffect` (shows the strength slider) |
|
||||
| `QualityOption.cs` | Resolution tier record `(Display, Fps, Bitrate, Width, Height)`; `Label` formats as `1080p60 (8Mbps)` — drives the bottom-bar dropdown and the output-rect math |
|
||||
| `StreamConfig.cs` | `StreamConfig` (note: `TargetBitrate`/`Resolution` defaults are stale — not yet wired to the dropdown), `StreamHealth` (bitrate/FPS/dropped/duration), `StreamStatus` enum |
|
||||
| `MicStatus.cs` | Footer mic indicator: `NotConnected` / `Connected` / `Problem` — the state behind the MIC button's status dot (red = no device/capture off, green = capturing, yellow = a requested mic failed: in use/unplugged) |
|
||||
|
||||
@@ -380,14 +380,24 @@ public sealed class CameraManager : IDisposable
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
// Caller holds _gate.
|
||||
// Caller holds _gate. A new key inherits the last frame + bitmap of a dropped variant with
|
||||
// the SAME effect (a strength change), so neither the stream nor the preview blanks for a
|
||||
// frame while the creator drags the strength slider.
|
||||
private static void SyncVariants(CameraSession session, HashSet<WebcamEffectKey> keys)
|
||||
{
|
||||
foreach (var stale in session.Effects.Keys.Where(k => !keys.Contains(k)).ToList())
|
||||
var dropped = session.Effects.Where(kv => !keys.Contains(kv.Key)).ToList();
|
||||
foreach (var (stale, _) in dropped)
|
||||
session.Effects.Remove(stale);
|
||||
foreach (var key in keys)
|
||||
if (!session.Effects.ContainsKey(key))
|
||||
session.Effects[key] = new EffectVariant();
|
||||
{
|
||||
if (session.Effects.ContainsKey(key)) continue;
|
||||
var heir = dropped.FirstOrDefault(kv => kv.Key.Effect == key.Effect).Value;
|
||||
session.Effects[key] = new EffectVariant
|
||||
{
|
||||
LatestFrame = heir?.LatestFrame,
|
||||
PreviewBitmap = heir?.PreviewBitmap,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleEffects(CameraSession session)
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
| 48 | Distribution & packaging: **MSIX + Store IAP** | 🔶 In progress — **✅ route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28** | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) |
|
||||
| 49 | Chat profanity filter (local, opt-in, non-persistent, user word list) | ☐ Queued (2026-09-27) | [`TASKS/task-49-chat-profanity-filter.md`](TASKS/task-49-chat-profanity-filter.md) |
|
||||
| 50 | One-button "Publish this video" (resumable upload of the local recording) | ☐ Queued (2026-09-28) | [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md) |
|
||||
| 51 | **Webcam background effects: Remove + Blur** | 🔶 **v1 requirement — in progress**: slice 1 engine shipped 2026-10-09 (bundled MediaPipe Selfie Segmenter → ONNX, verified vs Google's runtime; Remove); next: Blur → config → camera hookup → UI | [`TASKS/task-51-webcam-bg-effects.md`](TASKS/task-51-webcam-bg-effects.md) |
|
||||
| 51 | **Webcam background effects: Remove + Blur** | ✅ **Built 2026-10-09** (v1 requirement) — bundled MediaPipe Selfie Segmenter → ONNX (verified vs Google's runtime), Remove + Blur with strength, per-scene config persisted, one processing point in CameraManager, radio-pill UI in the webcam's Layers properties. Pending: creator's real-camera check | [`TASKS/task-51-webcam-bg-effects.md`](TASKS/task-51-webcam-bg-effects.md) |
|
||||
| 52 | **Zoom box (anchored 2× magnifier)** | ✅ Done — shipped 2026-10-08 (3 creator corrections during the build: loupe → anchored capture over the full composite, drawn last → never shows the dim shading) | [`TASKS/task-52-zoom-box.md`](TASKS/task-52-zoom-box.md) |
|
||||
| 53 | **Output-format verification: Shorts/TikTok geometry (9:16)** | ☐ Queued (2026-10-08) — output formats never tested end-to-end; pin + prove the vertical geometry | [`TASKS/task-53-output-formats.md`](TASKS/task-53-output-formats.md) |
|
||||
| 54 | **Marcom round: free-forever positioning + merch storefront** | 🔶 In progress — llamacasty.com copy live 2026-10-08 (pricing stripped); merch POD + app About links queued | [`TASKS/task-54-marcom-free-merch.md`](TASKS/task-54-marcom-free-merch.md) |
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
> **Booked 2026-10-08 by creator ruling** — *"we're doing the bg removal and blur for v1 - book it, Danno"*.
|
||||
> This **reverses** the 2026-10-05 "background removal: out of product" exclusion (TASK 3 item 19).
|
||||
|
||||
**Status:** 🔶 In progress — **slice 1 (engine: bundled model + runner + Remove) shipped 2026-10-09**.
|
||||
Next: slice 2 (Blur), then config + persistence, then the camera-path hookup + UI.
|
||||
**Status:** ✅ **Built 2026-10-09 — all five slices shipped** (engine, Blur, config + persistence,
|
||||
camera hookup, UI). Remaining: the creator's manual check on a real camera (quality, lighting,
|
||||
`startup.log` perf line).
|
||||
|
||||
**Goal:** per-webcam **Background Remove** and **Background Blur** on the live webcam layer,
|
||||
selected per element in its properties panel, composited into stream + recording like any
|
||||
@@ -72,7 +73,7 @@ delivery shape changed:
|
||||
- ✅ `WebcamSceneConfig` gains `BackgroundEffect` (`None | Remove | Blur`) + `BackgroundStrength` (slice 3).
|
||||
- ✅ Persist via the existing `WebcamSceneConfig` table path in `LayoutStore.Save.cs`/
|
||||
`Load.cs` (new columns + migration). Follow `LayoutStore.Migrations.cs` conventions.
|
||||
- ☐ **Properties UI (creator spec 2026-10-09):** in the webcam's properties in the **Layers
|
||||
- ✅ **Properties UI (creator spec 2026-10-09, slice 5):** in the webcam's properties in the **Layers
|
||||
panel**, two **pill switches** — "Remove background" and "Blur background" — using the existing
|
||||
`PillToggle` style (`Themes/Controls.xaml`, already used in `Controls/LeftPanel.xaml`). They act
|
||||
as **radio buttons**: turning one on turns the other off; at most one is active; both off =
|
||||
@@ -104,7 +105,7 @@ delivery shape changed:
|
||||
|
||||
- ✅ Separate work unit after the zoom build (zoom shipped 2026-10-08).
|
||||
- Slice order: 1 engine ✅ → 2 Blur ✅ → 3 config + persistence ✅ → 4 camera-path hookup ✅ (one
|
||||
processing point, both consumers) + perf check → 5 properties-panel UI.
|
||||
processing point, both consumers) + perf check → 5 properties-panel UI ✅.
|
||||
|
||||
## Related (not duplicated here)
|
||||
|
||||
|
||||
@@ -244,11 +244,18 @@ public partial class MainViewModel
|
||||
config.PropertyChanged += OnWebcamConfigEffectChanged;
|
||||
var key = EffectKeyFor(config);
|
||||
var bitmap = _cameraManager.GetPreviewBitmap(_webcam.DeviceId, key);
|
||||
if (bitmap != null || key.Effect != BackgroundEffect.None)
|
||||
config.VideoImageSource = bitmap;
|
||||
// A strength change within the same effect keeps showing the previous variant's
|
||||
// last frame until the new one renders (no blank flicker while dragging the slider);
|
||||
// switching INTO an effect shows nothing first, so Remove never flashes the room.
|
||||
var sameEffect = _shownEffect.TryGetValue(config, out var shown) && shown.Effect == key.Effect;
|
||||
_shownEffect[config] = key;
|
||||
if (bitmap != null) config.VideoImageSource = bitmap;
|
||||
else if (key.Effect != BackgroundEffect.None && !sameEffect) config.VideoImageSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<WebcamSceneConfig, WebcamEffectKey> _shownEffect = new();
|
||||
|
||||
private void OnWebcamConfigEffectChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is not (nameof(WebcamSceneConfig.BackgroundEffect) or nameof(WebcamSceneConfig.BackgroundStrength)))
|
||||
|
||||
@@ -663,6 +663,13 @@ This replaces the old five-seeder cluster (`Seed{Starting,Brb,Ending,Chat}Backgr
|
||||
per pixel) — the first naive per-pixel 2-D bilinear cost 46/80 ms. `CameraManager` logs the real
|
||||
average once per session after 120 processed frames (`startup.log`: "background effects avg …").
|
||||
`MainViewModel.PersonSegmenterOverride` is the hermetic-test seam.
|
||||
- **UI (slice 5, creator spec 2026-10-09):** the webcam's properties in the Layers panel
|
||||
(`Controls/LeftPanel.xaml`) gain a **Background** section — two `PillToggle` switches, Remove and
|
||||
Blur, bound to `RemoveBackground`/`BlurBackground` (radio-exclusive: one on turns the other off,
|
||||
turning the active one off = None) and a Strength slider (0..1, 5% snaps, visible only while an
|
||||
effect is on; Blur = amount, Remove = edge softness). Dragging the slider never blanks the webcam:
|
||||
a new strength variant inherits the dropped same-effect variant's last frame + bitmap
|
||||
(`CameraManager.SyncVariants`), and the VM keeps the shown bitmap until the new one renders.
|
||||
- **Tests:** `BackgroundEffectEngineTests` — the Remove integration test (fake segmenter), and a
|
||||
model contract test that runs the BUNDLED model through the C# preprocessing on a fixture and
|
||||
matches the MediaPipe-produced reference mask (≥99.5% agreement; fixture = MediaPipe's public test
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Xunit;
|
||||
using ytLive.Models;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>TASK 51 slice 5 — the creator's UI spec (2026-10-09): Remove and Blur are two pill
|
||||
/// switches in the webcam's Layers properties that act as radio buttons (one on turns the other
|
||||
/// off; both off = no effect), plus a strength slider.</summary>
|
||||
public class WebcamBackgroundPillTests
|
||||
{
|
||||
[Fact]
|
||||
public void Remove_and_Blur_pills_are_radio_exclusive_and_wired_in_the_webcam_properties()
|
||||
{
|
||||
// Behaviour: the bools the pills bind to.
|
||||
var config = new WebcamSceneConfig();
|
||||
var raised = new System.Collections.Generic.List<string?>();
|
||||
config.PropertyChanged += (_, e) => raised.Add(e.PropertyName);
|
||||
|
||||
config.RemoveBackground = true;
|
||||
Assert.Equal(BackgroundEffect.Remove, config.BackgroundEffect);
|
||||
Assert.True(config.HasBackgroundEffect);
|
||||
|
||||
raised.Clear();
|
||||
config.BlurBackground = true; // turning Blur on turns Remove off
|
||||
Assert.Equal(BackgroundEffect.Blur, config.BackgroundEffect);
|
||||
Assert.False(config.RemoveBackground);
|
||||
Assert.Contains(nameof(WebcamSceneConfig.RemoveBackground), raised); // Remove pill un-checks
|
||||
Assert.Contains(nameof(WebcamSceneConfig.BlurBackground), raised);
|
||||
|
||||
config.RemoveBackground = false; // turning the INACTIVE pill off changes nothing
|
||||
Assert.Equal(BackgroundEffect.Blur, config.BackgroundEffect);
|
||||
|
||||
config.BlurBackground = false; // turning the active pill off = None
|
||||
Assert.Equal(BackgroundEffect.None, config.BackgroundEffect);
|
||||
Assert.False(config.HasBackgroundEffect);
|
||||
|
||||
// Wiring: the two PillToggle switches + the strength slider live in the webcam's
|
||||
// properties section of the Layers panel, bound to those properties.
|
||||
XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";
|
||||
var doc = XDocument.Load(SourceAssert.RepoPath("Controls", "LeftPanel.xaml"));
|
||||
var webcamPanel = doc.Descendants()
|
||||
.Single(e => e.Name.LocalName == "StackPanel"
|
||||
&& ((string?)e.Attribute("Visibility") ?? "").Contains("SelectedElement.IsWebcam"));
|
||||
|
||||
XElement Named(string name) => webcamPanel.Descendants().Single(e => (string?)e.Attribute(x + "Name") == name);
|
||||
|
||||
foreach (var (pill, property) in new[] { ("RemoveBackgroundPill", "RemoveBackground"), ("BlurBackgroundPill", "BlurBackground") })
|
||||
{
|
||||
var toggle = Named(pill);
|
||||
Assert.Equal("ToggleButton", toggle.Name.LocalName);
|
||||
Assert.Contains("PillToggle", (string?)toggle.Attribute("Style"));
|
||||
Assert.Contains($"SelectedElement.{property}", (string?)toggle.Attribute("IsChecked"));
|
||||
Assert.Contains("TwoWay", (string?)toggle.Attribute("IsChecked"));
|
||||
}
|
||||
|
||||
var slider = Named("BackgroundStrengthSlider");
|
||||
Assert.Contains("SelectedElement.BackgroundStrength", (string?)slider.Attribute("Value"));
|
||||
Assert.Equal("0", (string?)slider.Attribute("Minimum"));
|
||||
Assert.Equal("1", (string?)slider.Attribute("Maximum"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user