Skip to content

fix(compositor): keep privacy blur on its content under zoom and 3D tilt - #679

Merged
EtienneLescot merged 3 commits into
mainfrom
fix/privacy-blur-follows-zoom
Sep 16, 2026
Merged

EtienneLescot merged 3 commits into
mainfrom
fix/privacy-blur-follows-zoom

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

A blur/mosaic annotation is a privacy mask, but it did not follow the footage it covers. It was drawn on the screen box without the zoom (s_ann), while the content is magnified in the zoomed box (s_dst) and, under a 3D preset, warped through the tilted quad. The mask stayed on a fixed output rectangle and what it hid slid out from under it — in the preview and in the exported file. On a D3D11 render with a 2× zoom, 89 % to 100 % of the covered content was visible.

FrameGeometry::privacy_mask now places the mask on the content, once, for all three backends:

  • Zoom: the mask sits on s_dst. The drawn cut is taken at zoom 1, so content at fraction f of the screen lands exactly at s_dst.xy + f * s_dst.wh.
  • 3D tilt: the mask corners go through the same TiltedQuad as the screen. Mode 10 gets the four corners in dst_prev (TL, TR) and src_prev (BR, BL) with mb.z = 1, and inverse-warps them with the existing quad_inverse_bilinear. HLSL, MSL and WGSL are changed identically; LayerCB keeps its 128-byte layout.
  • Motion blur: while velocity blur is on, the mask grows to the previous frame's rect, which contains the whole smear. An oval then falls back to its rectangle, because an ellipse inscribed in the widened box no longer contains the current one.
  • Edges: one resting-box pixel of padding per side, scaled with the magnification. The oval feather now sits outside the ellipse.
  • Strength: the blur radius and the mosaic block scale with the content's magnification, and never go below today's value. A 12 px mosaic over 2× content would otherwise only average 6 source pixels.

Text, arrows, images and captions keep their current anchoring.

Known follow-up, not in this PR: a selected blur's outline in the editor still sits on the unzoomed rect, so it disagrees with the rendered mask while the playhead is inside a zoom. The same mismatch already exists for ZoomFocusOverlay.

Related issue

None filed.

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Existing projects: blurs under a zoom now move and grow with the content. That is the fix, but it is a visible change.

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

No images attached. Set OPENSCREEN_PRIVACY_OUT when running privacy_blur_under_zoom to get one PPM per case.

Testing

Windows (D3D11), this machine

  • cargo test -p openscreen-compositor --lib: 209 passed (204 before + 5 new geometry tests).

  • The five geometry tests follow the content independently, through the cut and cut_ref. Three of them fail on the old placement (mutation run).

  • New env-gated pixel test tests/privacy_blur_under_zoom.rs renders a synthetic red box and counts the red pixels still visible under the mask:

    case before after
    rest 0 / 7 424 0 / 7 424
    zoom 2× centred 26 589 / 29 900 0
    zoom 2× off-centre 29 900 / 29 900 0
    zoom 2× + iso 19 774 / 20 158 0
    zoom 2× + left 17 500 / 21 560 0
  • App export, end to end: electron . export on eight legacy projects with the rebuilt addon (depth 4 zoom, with and without the mask). Red pixels at 3 s with the mask: 0 in every case (rest, centred, off-centre, iso). Without the mask: 42 777 to 207 933. Logged in manual-e2e-checklist.md.

  • npm run build-vite (tsc + vite), Biome on the touched TS file, npm run docs:check.

Linux, under WSL

  • Built inside nix develop .#compositor-view, with the pinned ffmpeg SDK and nix lavapipe.
  • cargo test -p openscreen-compositor --lib --tests: 227 passed. GPU tests ran on llvmpipe; the only skips were the three tests that need external memory, ONNX or VAAPI.
  • The WGSL also passes naga 30 validation. CI uses naga 24.

macOS

  • Not compiled locally. The Metal change mirrors the HLSL one line by line; every_shader_entry_point_compiles on the macOS job is the check.

Review

  • Adversarial review over geometry, shader parity, privacy semantics and tests.
  • It found the oval/motion-blur rim and the fixed pad; both are fixed in the second commit.

Not covered

  • The GUI editor preview: the HUD rendered as a black rectangle in computer-use screenshots, so the pass went through the CLI export. The export shares compose_frame with the preview.
  • Oval and Gaussian styles and motion-blurred ramps are unit-tested, not rendered.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Privacy blur and mosaic masks now remain aligned with zoomed and tilted screen content.
    • Privacy masks follow animated content more accurately during motion blur.
    • Blur strength and coverage now scale appropriately with screen magnification.
    • Oval privacy masks render with cleaner, more precise edges and automatically use rectangular coverage when needed.
  • Documentation
    • Clarified how annotations and privacy masks behave during zoom, tilt, and motion effects.

A blur/mosaic annotation was anchored on the unzoomed screen box (s_ann)
while the footage it covers is magnified in the zoomed box (s_dst) and,
under a 3D preset, warped through the tilted quad. The mask stayed on a
fixed output rectangle and the content it hid slid out from under it,
in the preview and in the export. Rendered on D3D11: with a 2x zoom,
up to 100% of the covered content was visible.

FrameGeometry::privacy_mask now places the mask on the content, once for
all three backends:
- on the zoomed box, where the zoom-independent cut puts the content;
- through the same TiltedQuad as the screen under a 3D preset, drawn by
  mode 10 with the corners in dst_prev/src_prev and mb.z = 1
  (HLSL, MSL and WGSL);
- widened to the previous frame's rect while velocity motion blur is on;
- padded by one pixel, with the oval feather moved outside the ellipse;
- with the blur radius / mosaic block scaled by the content's
  magnification, so a zoomed mosaic does not get finer.

Text, figures, images and captions keep their unzoomed anchor.

Tests: five geometry tests (they fail on the old placement) and an
env-gated D3D11 pixel test that renders a red secret under zoom, iso and
left presets and counts what stays visible.
Review of the previous commit found two places where the mask could
still under-cover its content:
- An oval inscribed in the rect widened to the motion-blur trail does
  not contain the current oval, so its rim showed through on zoom-ramp
  frames. A widened mask now falls back to its rectangle, like freehand.
- The 1 px pad covers the half-texel bilinear fringe only at rest; under
  a zoom the fringe grows with the content. The pad now scales with the
  magnification, on the upright and the tilted path.

Also corrects the doc sentence that put captions on the unzoomed box
(they are anchored on the output frame).
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e1f6d6ce-72ac-4abc-82e5-3e6786955261

📥 Commits

Reviewing files that changed from the base of the PR and between dc956d5 and 68f5035.

⛔ Files ignored due to path filters (1)
  • crates/compositor/src/shaders.hlsl is excluded by !**/*.hlsl
📒 Files selected for processing (11)
  • crates/compositor/src/compositor_linux.rs
  • crates/compositor/src/compositor_macos.rs
  • crates/compositor/src/compositor_windows.rs
  • crates/compositor/src/frame_geometry.rs
  • crates/compositor/src/scene.rs
  • crates/compositor/src/shaders.metal
  • crates/compositor/src/vk_shaders/layer.wgsl
  • crates/compositor/tests/privacy_blur_under_zoom.rs
  • src/native/sceneDescription.ts
  • technical-documentation/architecture/native-compositor.md
  • technical-documentation/testing/manual-e2e-checklist.md

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Privacy mask alignment

Layer / File(s) Summary
Privacy mask geometry and contracts
crates/compositor/src/frame_geometry.rs, crates/compositor/src/scene.rs
FrameGeometry::privacy_mask computes padded masks for zoom, tilt, frame-space annotations, and motion blur. SceneAnnotation::in_frame_space centralizes frame-space detection. Unit tests cover these cases.
Compositor privacy-mask integration
crates/compositor/src/compositor_linux.rs, crates/compositor/src/compositor_macos.rs, crates/compositor/src/compositor_windows.rs
All three backends pass FrameGeometry to annotation rendering and use privacy-mask geometry, strength, warp fields, and oval eligibility.
Warped shader mask rendering
crates/compositor/src/shaders.metal, crates/compositor/src/vk_shaders/layer.wgsl
Mode-10 shaders inverse-warp tilted masks and move oval feathering to the ellipse boundary.
Integration validation and behavior documentation
crates/compositor/tests/privacy_blur_under_zoom.rs, src/native/sceneDescription.ts, technical-documentation/architecture/native-compositor.md, technical-documentation/testing/manual-e2e-checklist.md
The Windows integration test checks leakage across zoom and tilt cases. Native documentation and the manual checklist describe the updated behavior and test results.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 68f50

No actionable privacy-mask regression is established from the available evidence, so the change is ready for normal merge checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 7 files. (4 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: keeping privacy blur aligned with content during zoom and 3D tilt.
Description check ✅ Passed The description follows the required template and provides a clear summary, issue status, change classification, release impact, platform impact, testing details, limitations, and known follow-up work…
Full details: Docstring Coverage

Explanation

Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 7 files. (4 skipped: 4 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/privacy-blur-follows-zoom

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot
EtienneLescot marked this pull request as draft September 16, 2026 09:11
@EtienneLescot
EtienneLescot marked this pull request as ready for review September 16, 2026 09:20
@EtienneLescot
EtienneLescot merged commit 300e9a7 into main Sep 16, 2026
23 checks passed
@EtienneLescot
EtienneLescot deleted the fix/privacy-blur-follows-zoom branch September 16, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant