Skip to content

Add webcam mask shapes for picture-in-picture overlays#304

Open
justynroberts wants to merge 1 commit intosiddharthvaddem:mainfrom
justynroberts:codex/webcam-mask-shapes
Open

Add webcam mask shapes for picture-in-picture overlays#304
justynroberts wants to merge 1 commit intosiddharthvaddem:mainfrom
justynroberts:codex/webcam-mask-shapes

Conversation

@justynroberts
Copy link
Copy Markdown

@justynroberts justynroberts commented Apr 3, 2026

Summary

Adds configurable webcam mask shapes for picture-in-picture overlays.

This introduces a new webcam mask shape setting with support for Rounded Rectangle and Circle.
The selected shape now stays consistent across live editor preview, video export, GIF export, and saved project state.

Vertical stack remains unchanged and continues to use the existing rectangular treatment.

Changes

  • add webcamMaskShape to editor state and project persistence
  • add shape selection to the layout settings UI
  • support circular webcam layout sizing in composite layout calculation
  • render circle masks consistently in preview and export paths
  • add/update tests for composite layout and project persistence
  • add i18n strings for the new shape selector

Validation

  • npm test -- src/lib/compositeLayout.test.ts src/components/video-editor/projectPersistence.test.ts
  • ./node_modules/.bin/tsc --noEmit

Notes

  • Existing behavior remains the default via rounded-rectangle
  • Project format version was bumped to include the new persisted field

Summary by CodeRabbit

Release Notes

  • New Features
    • Added configurable webcam mask shape option in settings, allowing users to choose between circle or rounded rectangle shapes for their webcam display.
    • Webcam mask shape preferences are persisted with project files.
    • When using vertical-stack layout, the webcam shape is automatically set to rounded rectangle.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

This pull request adds a webcamMaskShape configuration option to the video editor, allowing users to choose between "circle" and "rounded-rectangle" shapes for the webcam overlay. The feature is integrated into the editor state, settings UI, layout computation, project persistence (version 3), and export pipelines.

Changes

Cohort / File(s) Summary
Core Layout & Type Definitions
src/lib/compositeLayout.ts, src/components/video-editor/types.ts, src/components/video-editor/videoPlayback/layoutUtils.ts
Introduced WebcamMaskShape union type and WEBCAM_MASK_SHAPES constant; added resolveWebcamMaskShape() helper; extended StyledRenderRect with shape field; updated computeCompositeLayout() to accept and process webcamMaskShape with shape-specific sizing logic for circles; added webcamMaskShape to LayoutParams.
State Management
src/hooks/useEditorHistory.ts, src/components/video-editor/projectPersistence.ts, src/components/video-editor/projectPersistence.test.ts
Added webcamMaskShape to EditorState interface and initialized with default; extended ProjectEditorState with normalization for shape values; bumped PROJECT_VERSION from 2 to 3; updated test expectations for versioned project data.
UI & Editor Integration
src/components/video-editor/SettingsPanel.tsx, src/components/video-editor/VideoEditor.tsx
Added shape selection control to settings panel with conditional disable for vertical-stack layout; wired webcamMaskShape through editor state, history pushes, and project snapshots; passed shape to VideoPlayback and exporters.
Rendering & Playback
src/components/video-editor/VideoPlayback.tsx
Updated to accept and forward webcamMaskShape to layout utilities; added dynamic CSS borderRadius computation based on shape (50% for circle, preset value otherwise).
Export Configuration
src/lib/exporter/frameRenderer.ts, src/lib/exporter/gifExporter.ts, src/lib/exporter/videoExporter.ts
Added optional webcamMaskShape property to renderer and exporter configs; threaded shape parameter through export pipeline to computeCompositeLayout().
Internationalization
src/i18n/locales/en/settings.json, src/i18n/locales/es/settings.json, src/i18n/locales/zh-CN/settings.json
Added translation keys for shape selection UI (shape, selectShape, roundedRectangle, circle) and corrected formatting in layout section.
Layout Tests
src/lib/compositeLayout.test.ts
Updated vertical-stack assertions to reflect full-canvas fill behavior; added new test case verifying circle shape produces square dimensions and correct border radius.

Sequence Diagram

sequenceDiagram
    actor User
    participant SettingsPanel
    participant VideoEditor
    participant EditorHistory
    participant VideoPlayback
    participant LayoutUtils
    participant CompositeLayout
    participant FrameRenderer

    User->>SettingsPanel: Select webcam shape
    SettingsPanel->>VideoEditor: onWebcamMaskShapeChange(shape)
    VideoEditor->>EditorHistory: pushState({webcamMaskShape})
    EditorHistory->>VideoEditor: Update editor state
    
    VideoEditor->>VideoPlayback: Pass webcamMaskShape prop
    VideoPlayback->>LayoutUtils: layoutVideoContentUtil({webcamMaskShape})
    LayoutUtils->>CompositeLayout: computeCompositeLayout({webcamMaskShape})
    CompositeLayout->>CompositeLayout: resolveWebcamMaskShape()
    CompositeLayout->>CompositeLayout: Calculate shape-specific dimensions
    CompositeLayout-->>LayoutUtils: WebcamCompositeLayout with shape
    LayoutUtils-->>VideoPlayback: Layout result with webcamRect
    VideoPlayback->>VideoPlayback: Apply CSS borderRadius based on shape
    VideoPlayback-->>User: Render webcam with updated shape
    
    VideoEditor->>FrameRenderer: Pass webcamMaskShape to exporter
    FrameRenderer->>CompositeLayout: computeCompositeLayout({webcamMaskShape})
    CompositeLayout-->>FrameRenderer: Layout with shape applied
    FrameRenderer-->>User: Export with correct shape rendering
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

Poem

🐰 A circle here, a rounded rect there,
Webcam shapes float through the editor's air!
From settings to state, through layouts so grand,
The shape-shifting overlay bends to your hand! 🎬✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding configurable webcam mask shapes for picture-in-picture overlays, which is the core feature of this PR.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering summary, changes, validation, and notes. However, it deviates from the template structure by not explicitly checking the required sections like Type of Change, Related Issue(s), and Screenshots/Video.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@justynroberts justynroberts marked this pull request as ready for review April 3, 2026 14:33
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/exporter/frameRenderer.ts (1)

671-701: ⚠️ Potential issue | 🟠 Major

Circle mask shape not rendered in export.

The compositeWithShadows method always uses ctx.roundRect() for the webcam mask (lines 676-682) without checking webcamRect.shape. The preview renders circles correctly using CSS (borderRadius: 50%), but the export canvas rendering ignores the shape property, preventing circle masks from being applied during video/GIF export.

ctx.roundRect(
	webcamRect.x,
	webcamRect.y,
	webcamRect.width,
	webcamRect.height,
	webcamRect.borderRadius,
);

When webcamMaskShape is "circle", the shape should be rendered using canvas arc operations or alternative clipping path instead of roundRect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/exporter/frameRenderer.ts` around lines 671 - 701, The
compositeWithShadows block always draws the webcam mask with ctx.roundRect,
ignoring webcamRect.shape (or webcamMaskShape) so circle masks aren't exported;
update the rendering to branch on the mask shape inside compositeWithShadows (or
the method that contains the shown code) and when the shape is "circle" build
the clipping path with arc/ellipse (center = webcamRect.x + width/2,
webcamRect.y + height/2; radius = Math.min(width,height)/2) (or ctx.ellipse)
instead of ctx.roundRect, apply the same shadow/fill/clip logic used for
rounded-rects, and fall back to the existing ctx.roundRect for other shapes;
reference webcamRect, ctx.roundRect, and getWebcamLayoutPresetDefinition to
locate and implement the change.
🧹 Nitpick comments (1)
src/lib/compositeLayout.test.ts (1)

36-78: Consider adding a portrait vertical-stack test case as the primary scenario.

Current assertions validate a degenerate landscape stack geometry (e.g., zero-height screen area). That’s fine as an edge case, but adding a portrait case would better protect the expected user flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/compositeLayout.test.ts` around lines 36 - 78, The current tests for
computeCompositeLayout only cover a degenerate landscape "vertical-stack"
arrangement; add a new test case that uses a portrait canvas (e.g., canvasSize
{width:1080, height:1920}) and both screenSize and webcamSize present to assert
the normal vertical-stack behavior (non-zero screenRect and webcamRect heights,
proper x/y positions, widths matching canvas width, and expected
borderRadius/shape on webcamRect). Locate the test suite in
compositeLayout.test.ts and add a test (named e.g., "fills the canvas with the
combined screen and webcam stack in portrait vertical-stack mode") that calls
computeCompositeLayout with layoutPreset: "vertical-stack" and checks screenRect
and webcamRect are non-null and have sensible portrait split dimensions and
positioning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/lib/exporter/frameRenderer.ts`:
- Around line 671-701: The compositeWithShadows block always draws the webcam
mask with ctx.roundRect, ignoring webcamRect.shape (or webcamMaskShape) so
circle masks aren't exported; update the rendering to branch on the mask shape
inside compositeWithShadows (or the method that contains the shown code) and
when the shape is "circle" build the clipping path with arc/ellipse (center =
webcamRect.x + width/2, webcamRect.y + height/2; radius =
Math.min(width,height)/2) (or ctx.ellipse) instead of ctx.roundRect, apply the
same shadow/fill/clip logic used for rounded-rects, and fall back to the
existing ctx.roundRect for other shapes; reference webcamRect, ctx.roundRect,
and getWebcamLayoutPresetDefinition to locate and implement the change.

---

Nitpick comments:
In `@src/lib/compositeLayout.test.ts`:
- Around line 36-78: The current tests for computeCompositeLayout only cover a
degenerate landscape "vertical-stack" arrangement; add a new test case that uses
a portrait canvas (e.g., canvasSize {width:1080, height:1920}) and both
screenSize and webcamSize present to assert the normal vertical-stack behavior
(non-zero screenRect and webcamRect heights, proper x/y positions, widths
matching canvas width, and expected borderRadius/shape on webcamRect). Locate
the test suite in compositeLayout.test.ts and add a test (named e.g., "fills the
canvas with the combined screen and webcam stack in portrait vertical-stack
mode") that calls computeCompositeLayout with layoutPreset: "vertical-stack" and
checks screenRect and webcamRect are non-null and have sensible portrait split
dimensions and positioning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47ffeca1-455e-4ec6-99fa-b89bd476b53d

📥 Commits

Reviewing files that changed from the base of the PR and between b101820 and 96432b7.

📒 Files selected for processing (16)
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/VideoPlayback.tsx
  • src/components/video-editor/projectPersistence.test.ts
  • src/components/video-editor/projectPersistence.ts
  • src/components/video-editor/types.ts
  • src/components/video-editor/videoPlayback/layoutUtils.ts
  • src/hooks/useEditorHistory.ts
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • src/lib/compositeLayout.test.ts
  • src/lib/compositeLayout.ts
  • src/lib/exporter/frameRenderer.ts
  • src/lib/exporter/gifExporter.ts
  • src/lib/exporter/videoExporter.ts

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