Skip to content

Add webcam mask shape support#288

Merged
siddharthvaddem merged 1 commit intosiddharthvaddem:mainfrom
gulivan:feature/webcam-mask-shapes
Apr 4, 2026
Merged

Add webcam mask shape support#288
siddharthvaddem merged 1 commit intosiddharthvaddem:mainfrom
gulivan:feature/webcam-mask-shapes

Conversation

@gulivan
Copy link
Copy Markdown
Contributor

@gulivan gulivan commented Apr 2, 2026

Summary

  • Adds configurable webcam mask shapes (circle, rounded rectangle, etc.) to the video editor
  • Implements webcamMaskShapes.ts with shape definitions and rendering logic
  • Integrates mask shape selection into SettingsPanel, VideoPlayback, composite layout, and export pipeline
  • Includes i18n support (en, es, zh-CN) and updated tests

Demo:

export-1775164482142.mp4

Test plan

  • Verify mask shape options appear in the Settings panel
  • Confirm each shape renders correctly in the video preview
  • Test that exported videos/GIFs apply the selected mask shape
  • Run existing tests (compositeLayout.test.ts, projectPersistence.test.ts)

Summary by CodeRabbit

Release Notes

  • New Features
    • Added webcam mask shape customization in picture-in-picture layout mode, allowing users to select from rectangle, circle, square, or rounded shapes.
    • Webcam mask shape selections are now persisted in projects and applied during playback and export.
    • Added multilingual support for mask shape settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

This PR adds configurable webcam mask shape support ("rectangle", "circle", "square", "rounded") to the video editor. Changes include new UI controls in SettingsPanel, state management through VideoEditor and projectPersistence, rendering updates in VideoPlayback with CSS clip-path, canvas utilities, and layout computation adjustments.

Changes

Cohort / File(s) Summary
Type Definitions & Utilities
src/components/video-editor/types.ts, src/lib/webcamMaskShapes.ts
Introduced WebcamMaskShape union type, DEFAULT_WEBCAM_MASK_SHAPE constant, and two utility functions: getCssClipPath() for CSS clip-path generation and drawCanvasClipPath() for canvas-based clipping with shape-specific geometry.
Settings UI
src/components/video-editor/SettingsPanel.tsx
Added webcamMaskShape prop and onWebcamMaskShapeChange callback; created new conditional UI control in "layout" section rendering four selectable mask-shape options when picture-in-picture preset is active.
State Management
src/components/video-editor/VideoEditor.tsx, src/components/video-editor/projectPersistence.ts, src/components/video-editor/projectPersistence.test.ts, src/hooks/useEditorHistory.ts
Extended EditorState and ProjectEditorState with webcamMaskShape field; added state persistence via pushState, project snapshots, and undo/redo; implemented normalization logic to validate and default invalid shapes to "rectangle"; added test coverage for normalization.
Rendering & Layout
src/components/video-editor/VideoPlayback.tsx, src/lib/compositeLayout.ts, src/lib/compositeLayout.test.ts, src/components/video-editor/videoPlayback/layoutUtils.ts
Threaded webcamMaskShape through layout computation; updated VideoPlayback to apply CSS clip-path or borderRadius with shape-specific styling; adjusted webcam rect dimensions for "circle" and "square" shapes; added comprehensive test coverage for mask geometry assertions.
Export & Frame Rendering
src/lib/exporter/frameRenderer.ts, src/lib/exporter/gifExporter.ts, src/lib/exporter/videoExporter.ts
Extended FrameRenderConfig, GifExporterConfig, and VideoExporterConfig with optional webcamMaskShape field; updated frame renderer to use drawCanvasClipPath() helper instead of inline ctx.roundRect() calls for shape-aware clipping.
Internationalization
src/i18n/locales/en/settings.json, src/i18n/locales/es/settings.json, src/i18n/locales/zh-CN/settings.json
Added new translation key layout.webcamShape ("Camera Shape" / "Forma de cámara" / "摄像头形状") across English, Spanish, and Simplified Chinese locales.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant SettingsPanel
    participant VideoEditor
    participant ProjectPersistence
    participant VideoPlayback
    participant LayoutUtils
    participant CompositeLayout

    User->>SettingsPanel: Select webcam mask shape<br/>(circle, square, etc.)
    SettingsPanel->>VideoEditor: onWebcamMaskShapeChange(shape)
    VideoEditor->>VideoEditor: pushState({webcamMaskShape: shape})
    VideoEditor->>ProjectPersistence: createProjectData(editor)
    ProjectPersistence->>ProjectPersistence: Persist webcamMaskShape
    
    VideoEditor->>VideoPlayback: Re-render with webcamMaskShape prop
    VideoPlayback->>LayoutUtils: layoutVideoContentUtil(..., webcamMaskShape)
    LayoutUtils->>CompositeLayout: computeCompositeLayout({..., webcamMaskShape})
    CompositeLayout->>CompositeLayout: Calculate shape-specific<br/>dimensions & borderRadius
    CompositeLayout-->>VideoPlayback: Return webcamRect with maskShape
    
    VideoPlayback->>VideoPlayback: Apply CSS clip-path<br/>or borderRadius based on shape
    VideoPlayback-->>User: Render webcam with mask shape
Loading
sequenceDiagram
    participant VideoEditor
    participant Exporter
    participant FrameRenderer
    participant Canvas
    participant WebcamMaskShapes

    VideoEditor->>Exporter: Export with webcamMaskShape config
    Exporter->>FrameRenderer: new FrameRenderer({..., webcamMaskShape})
    
    FrameRenderer->>Canvas: Render frame with webcam
    Canvas->>FrameRenderer: requestAnimationFrame callback
    FrameRenderer->>Canvas: computeCompositeLayout({..., webcamMaskShape})
    
    FrameRenderer->>WebcamMaskShapes: drawCanvasClipPath(ctx, x, y, w, h,<br/>shape, borderRadius)
    WebcamMaskShapes->>Canvas: ctx.beginPath()<br/>ctx.arc() or ctx.roundRect()<br/>ctx.closePath()
    Canvas->>Canvas: ctx.clip()
    Canvas->>Canvas: ctx.drawImage(webcam)
    
    FrameRenderer-->>VideoEditor: Frame with masked webcam rendered
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • openscreen#229: Adds webcam overlay support with similar modifications to VideoPlayback, VideoEditor, projectPersistence, and exporter/frame-rendering interfaces, indicating parallel feature development.

Suggested reviewers

  • siddharthvaddem

Poem

🐰 A shape-shifter's delight, the webcam now bends,
Round circles and squares, where the masking commences,
With clip-paths and canvas, the rendering transcends,
Your frame-in-a-frame finds new aesthetic defences! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description lacks key required template sections including Motivation, Type of Change, Related Issue(s), and Testing details. While it provides a summary and demo, it does not follow the repository's PR template structure. Add the missing sections from the template: expand Motivation to explain why mask shapes are needed, explicitly mark Type of Change, link any related issues, and detail testing steps beyond the checklist items.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add webcam mask shape support' directly summarizes the main change: introducing configurable webcam mask shapes to the video editor.

✏️ 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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d0ccf3bde

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/lib/exporter/videoExporter.ts (1)

35-35: Consider importing WebcamMaskShape at the top with other types.

Same as in gifExporter.ts — the inline import type is inconsistent with how other types from @/components/video-editor/types are imported (Lines 1-8).

♻️ Suggested refactor
 import type {
 	AnnotationRegion,
 	CropRegion,
 	SpeedRegion,
 	TrimRegion,
 	WebcamLayoutPreset,
+	WebcamMaskShape,
 	ZoomRegion,
 } from "@/components/video-editor/types";

Then update Line 35:

-	webcamMaskShape?: import("@/components/video-editor/types").WebcamMaskShape;
+	webcamMaskShape?: WebcamMaskShape;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/exporter/videoExporter.ts` at line 35, The inline type import for
webcamMaskShape should be replaced by importing WebcamMaskShape alongside the
other types at the top of videoExporter.ts; update the top imports to include
WebcamMaskShape from "@/components/video-editor/types" and change the
webcamMaskShape property declaration (webcamMaskShape?:
import("@/components/video-editor/types").WebcamMaskShape;) to use the imported
WebcamMaskShape type instead.
src/lib/exporter/gifExporter.ts (1)

44-44: Consider importing WebcamMaskShape at the top with other types.

The inline import type syntax works but is inconsistent with how other types from @/components/video-editor/types are imported (Lines 2-8). Moving it to the top improves readability and consistency.

♻️ Suggested refactor
 import type {
 	AnnotationRegion,
 	CropRegion,
 	SpeedRegion,
 	TrimRegion,
 	WebcamLayoutPreset,
+	WebcamMaskShape,
 	ZoomRegion,
 } from "@/components/video-editor/types";

Then update Line 44:

-	webcamMaskShape?: import("@/components/video-editor/types").WebcamMaskShape;
+	webcamMaskShape?: WebcamMaskShape;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/exporter/gifExporter.ts` at line 44, The inline type import for
WebcamMaskShape should be moved to the top alongside the other imports from
"@/components/video-editor/types" for consistency and readability; add
WebcamMaskShape to the existing import list (the same statement that currently
imports other types from "@/components/video-editor/types") and then replace the
inline reference "webcamMaskShape?:
import(\"@/components/video-editor/types\").WebcamMaskShape;" with
"webcamMaskShape?: WebcamMaskShape;" in the gifExporter.ts declaration.
src/lib/webcamMaskShapes.ts (1)

40-45: Verify roundRect browser support for your target environments.

CanvasRenderingContext2D.roundRect() is widely available: Chrome/Edge 99+, Firefox 112+, Safari 16.4+, and Opera 85+ (all supporting since April 2023). For older browser support, consider a polyfill or manual path drawing implementation.

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

In `@src/lib/webcamMaskShapes.ts` around lines 40 - 45, The code uses
CanvasRenderingContext2D.roundRect (ctx.roundRect) in the switch cases for
"rectangle"/"rounded"/"square"/default which may not exist in older browsers;
add a fallback that detects if ctx.roundRect is undefined and then draws a
rounded-rect path manually (using ctx.beginPath, moveTo/lineTo/arcTo or arc for
corners, closePath, and ctx.fill()/ctx.stroke() as appropriate) or include a
small polyfill that defines ctx.roundRect before use; update the switch branch
in webcamMaskShapes (where ctx.roundRect is called) to call the fallback
function (e.g., drawRoundedRect(ctx, x, y, w, h, borderRadius)) when
ctx.roundRect is unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 631-711: The shape option labels are hardcoded and the button
group doesn't expose pressed state; update the shape list used in the
webcamLayoutPreset === "picture-in-picture" block (the array mapped to render
buttons) to use localization keys via t(...) instead of hardcoded
"Rect"/"Circle"/"Square"/"Rounded", and add aria-pressed={webcamMaskShape ===
shape.value} to each button rendered by the map so assistive tech can detect the
selected state; ensure the onClick still calls
onWebcamMaskShapeChange(shape.value) and keep existing className logic using
webcamMaskShape for visual selection.

---

Nitpick comments:
In `@src/lib/exporter/gifExporter.ts`:
- Line 44: The inline type import for WebcamMaskShape should be moved to the top
alongside the other imports from "@/components/video-editor/types" for
consistency and readability; add WebcamMaskShape to the existing import list
(the same statement that currently imports other types from
"@/components/video-editor/types") and then replace the inline reference
"webcamMaskShape?: import(\"@/components/video-editor/types\").WebcamMaskShape;"
with "webcamMaskShape?: WebcamMaskShape;" in the gifExporter.ts declaration.

In `@src/lib/exporter/videoExporter.ts`:
- Line 35: The inline type import for webcamMaskShape should be replaced by
importing WebcamMaskShape alongside the other types at the top of
videoExporter.ts; update the top imports to include WebcamMaskShape from
"@/components/video-editor/types" and change the webcamMaskShape property
declaration (webcamMaskShape?:
import("@/components/video-editor/types").WebcamMaskShape;) to use the imported
WebcamMaskShape type instead.

In `@src/lib/webcamMaskShapes.ts`:
- Around line 40-45: The code uses CanvasRenderingContext2D.roundRect
(ctx.roundRect) in the switch cases for "rectangle"/"rounded"/"square"/default
which may not exist in older browsers; add a fallback that detects if
ctx.roundRect is undefined and then draws a rounded-rect path manually (using
ctx.beginPath, moveTo/lineTo/arcTo or arc for corners, closePath, and
ctx.fill()/ctx.stroke() as appropriate) or include a small polyfill that defines
ctx.roundRect before use; update the switch branch in webcamMaskShapes (where
ctx.roundRect is called) to call the fallback function (e.g.,
drawRoundedRect(ctx, x, y, w, h, borderRadius)) when ctx.roundRect is
unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45695159-3486-4219-a24d-749c90b97808

📥 Commits

Reviewing files that changed from the base of the PR and between 2f36160 and 9d0ccf3.

📒 Files selected for processing (17)
  • 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
  • src/lib/webcamMaskShapes.ts

@siddharthvaddem
Copy link
Copy Markdown
Owner

awesome! while you are at it, can we also have this #285 fixed? Shape and size go hand in hand. Would really appreciate it

@gulivan
Copy link
Copy Markdown
Contributor Author

gulivan commented Apr 3, 2026

Hi!
I see it's already implemented in #289 so I'm not quite sure about the next steps
#285

@siddharthvaddem
Copy link
Copy Markdown
Owner

okay makes sense - thanks for working on this

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.

2 participants