feat(compositor): add circular crop for Loom-style webcam PIP overlays#176
Merged
streamer45 merged 6 commits intomainfrom Mar 20, 2026
Merged
feat(compositor): add circular crop for Loom-style webcam PIP overlays#176streamer45 merged 6 commits intomainfrom
streamer45 merged 6 commits intomainfrom
Conversation
Add a crop_circle boolean field to compositor LayerConfig that clips the composited layer to an ellipse inscribed in the destination rect. When the rect is square this produces a perfect circle — ideal for webcam PIP overlays in the style of Loom. Backend: - config.rs, kernel.rs, mod.rs: thread crop_circle through LayerConfig, ResolvedLayer, LayerSnapshot, CompositeItem, and resolve_scene - blit.rs: add ellipse-masked blit path with anti-aliased edges (~1.5px smoothstep band) to both scale_blit_rgba and scale_blit_rgba_rotated; disable SIMD fast-forward when crop_circle is active - tests.rs: add 3 new tests for axis-aligned, rotated, and full composite_frame circular crop Frontend: - compositor-types.ts: add crop_circle to LayerConfig and ResolvedLayer - compositorConstants.ts: DEFAULT_CROP_CIRCLE = false - compositorLayerParsers.ts: parse/serialize cropCircle <-> crop_circle - compositorAtoms.ts: include cropCircle in layerEqual comparison - compositorOverlays.ts, useCompositorLayers.ts: thread cropCircle - compositorNodeWidgets.tsx: add Circle toggle (MirrorButton) in CropZoomControl; include in reset - compositorNodeInspector.tsx: pass cropCircle prop - compositorCanvasLayers.tsx: CSS border-radius: 50% preview - compositorServerSync.ts: sync cropCircle from server state Demo: - samples/pipelines/dynamic/video_moq_webcam_circle_pip.yml Signed-off-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…State comparator Address two bugs found by Devin Review: 1. kernel.rs: The skip_clear optimization now checks crop_circle — when true, pixels outside the ellipse must be cleared to transparent black. Without this, pooled buffers would leak stale frame data in the corners. Added a regression test using VideoFramePool with pre-filled garbage. 2. useCompositorLayers.ts: The hasExtraChanges comparator passed to mergeOverlayState now includes cropCircle, so remote config updates that only change cropCircle are no longer silently discarded. Signed-off-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
Remove overflow:hidden from the VideoLayer container when crop_circle is enabled. The overflow:hidden was clipping resize handles positioned at corners since they fall outside the inscribed ellipse. The circular outline (via borderRadius: 50%) alone provides sufficient visual preview of the crop circle shape. Signed-off-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
…tack Replace the boolean crop_circle field with an extensible CropShape enum (Rect | Circle) throughout backend and frontend. This enables future shape variants (RoundedRect, Hexagon, etc.) without breaking changes. Backend: - Add CropShape enum with serde rename_all snake_case - Replace crop_circle: bool with crop_shape: CropShape in LayerConfig, ResolvedLayer, LayerSnapshot, CompositeItem, ResolvedSlotConfig - Kernel converts enum to bool at blit call site - Update all tests and benchmarks Frontend: - Replace cropCircle: boolean with cropShape: 'rect' | 'circle' in LayerState, parsers, atoms, overlays, server sync, drag resize - Replace On/Off MirrorButton toggle with segmented control (▭ Rect / ● Circle) matching existing design patterns - Update DEFAULT_CROP_CIRCLE to DEFAULT_CROP_SHAPE - Update all test files Pipeline: - Update demo pipeline YAML to use crop_shape: rect/circle Signed-off-by: Devin AI <devin@devin.ai> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
The doc comment claimed unknown values deserialize as Rect, but there was no #[serde(other)] to back that up. Corrected the comment to accurately describe the actual behavior: field-level #[serde(default)] handles missing keys, but unknown variant strings will error. Signed-off-by: Devin AI <devin@devin.ai> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
…loop ellipse_sx, ellipse_sy, aa_band, and aa_inner only depend on the destination rect dimensions (rw/rh) which are loop-invariant. Move them before the per-row closure, matching the axis-aligned path which already hoists these correctly. Signed-off-by: Devin AI <devin@devin.ai> Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add circular cropping to the compositor node for Loom-style webcam PIP overlays, using an extensible
CropShapeenum instead of a boolean.Backend (Rust)
CropShapeenum (Rect|Circle) withserde(rename_all = "snake_case")and#[default] Rect— extensible toRoundedRect,Hexagon, etc.crop_shape != Rectso pooled buffer data doesn't leak in ellipse cornersFrontend (React/TypeScript)
▭ Rect/● Circle) replaces the boolean On/Off toggle — matches existing MirrorButton design patternscropShape: 'rect' | 'circle'throughout LayerState, parsers, atoms, overlays, server sync, canvas layersborderRadius: 50%canvas preview for instant WYSIWYG feedbackmergeOverlayStatecomparator includescropShape(fixes silent drop of remote-only changes)Demo pipeline
samples/pipelines/dynamic/video_moq_webcam_circle_pip.yml— screen-share + circular webcam PIPFiles changed (22 files, ~600 additions)
Backend:
config.rs,kernel.rs,mod.rs,blit.rs,tests.rs,compositor_only.rs(bench)Frontend:
compositor-types.ts,compositorConstants.ts,compositorLayerParsers.ts,compositorAtoms.ts,compositorOverlays.ts,useCompositorLayers.ts,compositorNodeWidgets.tsx,compositorNodeInspector.tsx,compositorCanvasLayers.tsx,compositorServerSync.tsTests:
compositorDragResize.test.ts,useCompositorLayers.monitor-flow.test.tsPipeline:
video_moq_webcam_circle_pip.ymlReview & Testing Checklist for Human
▭ Rect / ● Circletoggle in Crop & Zoom section works, resets correctly, and matches the existing MirrorButton styleborderRadius: 50%preview matches the actual composited output in Stream viewcrop_shapefield → verify it defaults torect(no regression)Recommended test plan
just skit+just ui)video_moq_webcam_circle_pip.ymlpipelineNotes
blit.rs) still usecrop_circle: boolinternally — the kernel converts enum→bool at the call site to avoid touching the complex SIMD codeCropShapederivesPartialEq, Eqfor comparison in skip_clear and blit branchingLink to Devin session: https://staging.itsdev.in/sessions/9615e2650647434e84e4147265a8f38c
Requested by: @streamer45