fix(web): composite consumes the FULL canvas, alpha crop is measure-only — transparent web margins reveal the webcam again
take-19-2/take-20 'webcam black square under the web widget': the capture was alpha-cropped (FindContentBounds) and that CROP was fed to the compositor, whose UniformToFill zoomed the opaque content box to cover the whole element rect the moment the widget drew any content (idle transparent page = full-frame crop = the correct viewport, hence take-1-good/take-2-bad). OBS model verified: the page is a fixed 1920x1080 canvas and the element rect is a viewport onto it — measure with the alpha crop (preview/selection), hand the composite the FULL canvas on an 8-deep ring + Epoch (same identity discipline as camera/screen). Regression test: Composite_FullCanvasWebSource_TransparentMarginsRevealWebcam (the reveal contract: margin pixel = webcam, badge pixel = widget).
This commit is contained in:
@@ -114,6 +114,70 @@ public class SceneCompositorTests
|
||||
AssertColor(output, 550, 700, 255, 255, 255);
|
||||
}
|
||||
|
||||
/// <summary>The ONE integration test for the web-overlay transparency regression
|
||||
/// (take-19-2/take-20 "black square over the webcam"): a FULL-canvas web frame
|
||||
/// (the page background is transparent; the widget content lives at its canvas
|
||||
/// position) is composited into a webcam-covered scene. The element rect is a
|
||||
/// viewport onto the page, so the page's transparent margins must REVEAL the
|
||||
/// layers beneath (the webcam), while the widget's opaque content still draws.
|
||||
/// Feeding the ALPHA CROP instead of the full canvas zoomed the opaque content
|
||||
/// box to cover the whole element rect — this test pins the reveal contract.</summary>
|
||||
[Fact]
|
||||
public void Composite_FullCanvasWebSource_TransparentMarginsRevealWebcam()
|
||||
{
|
||||
var red = Solid(1920, 1080, 255, 0, 0);
|
||||
var green = Solid(1280, 720, 0, 255, 0);
|
||||
var blue = Solid(1920, 1080, 0, 0, 255);
|
||||
|
||||
// FULL-canvas web frame (what WebView2Manager hands to the compositor):
|
||||
// transparent everywhere except an opaque badge at its canvas position.
|
||||
var webPixels = new byte[1920 * 1080 * 4];
|
||||
for (var y = 800; y < 880; y++)
|
||||
for (var x = 1400; x < 1520; x++)
|
||||
{
|
||||
var i = (y * 1920 + x) * 4;
|
||||
webPixels[i] = blue.BgraPixels[i];
|
||||
webPixels[i + 1] = blue.BgraPixels[i + 1];
|
||||
webPixels[i + 2] = blue.BgraPixels[i + 2];
|
||||
webPixels[i + 3] = 255;
|
||||
}
|
||||
var webCanvas = new VideoFrame(1920, 1080, webPixels);
|
||||
|
||||
var background = new Source { Type = SourceType.DisplayCapture, IsBackground = true, CaptureKey = "monitor:0" };
|
||||
var webcam = new WebcamSceneConfig { X = 1360, Y = 760, Width = 536, Height = 294, ClipShape = ClipShape.Traditional };
|
||||
var webSource = new Source { Type = SourceType.WebSource, X = 1279, Y = 709, Width = 703, Height = 389 };
|
||||
|
||||
var scene = new Scene { Name = "Live" };
|
||||
scene.Elements.Add(background);
|
||||
scene.Elements.Add(webcam);
|
||||
scene.Elements.Add(webSource);
|
||||
|
||||
VideoFrame? FrameFor(SceneElement e) => e switch
|
||||
{
|
||||
WebcamSceneConfig => green,
|
||||
Source { IsBackground: true } => red,
|
||||
Source { Type: SourceType.WebSource } => webCanvas,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
var options = new CompositorOptions
|
||||
{
|
||||
SourceRectX = 0, SourceRectY = 0, SourceRectWidth = 1920, SourceRectHeight = 1080,
|
||||
OutputWidth = 1920, OutputHeight = 1080,
|
||||
};
|
||||
|
||||
var output = new SceneCompositor().Render(scene, FrameFor, null, options);
|
||||
|
||||
// (1777,984) ≈ canvas (1360,760): inside the web element rect AND inside the
|
||||
// webcam rect, but in the page's transparent margin -> the WEBCAM must show.
|
||||
AssertColor(output, 1777, 984, 0, 255, 0);
|
||||
// (1810,1010) ≈ canvas (1450,825): inside the widget's opaque badge -> the
|
||||
// widget content draws over the webcam.
|
||||
AssertColor(output, 1810, 1010, 0, 0, 255);
|
||||
// deep margin north of the webcam: the backdrop shows through untouched.
|
||||
AssertColor(output, 1300, 720, 255, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>The take-4 memcpy fast path: a full-cover opaque backdrop (the live
|
||||
/// capture's contract) must reproduce the source EXACTLY, and when supplied via the
|
||||
/// <c>scratch</c> pool parameter the render must overwrite every byte of the reused
|
||||
|
||||
Reference in New Issue
Block a user