diff --git a/src/components/launch/FloatingWebcamPreviewView.tsx b/src/components/launch/FloatingWebcamPreviewView.tsx
index 2efe49f9..fd9b937a 100644
--- a/src/components/launch/FloatingWebcamPreviewView.tsx
+++ b/src/components/launch/FloatingWebcamPreviewView.tsx
@@ -1,4 +1,3 @@
-import { getWebcamGlassRimImage } from "../video-editor/webcamGlassRim";
import type { MouseEventHandler, ReactNode } from "react";
import type { useWebcamPreviewOverlay } from "./hooks/useWebcamPreviewOverlay";
import styles from "./LaunchWindow.module.css";
@@ -67,14 +66,6 @@ export function FloatingWebcamPreviewView({
clipPath: "circle(50%)",
}}
/>
-
);
diff --git a/src/components/launch/popovers/WebcamPopover.tsx b/src/components/launch/popovers/WebcamPopover.tsx
index 43e38e9b..d27e61b7 100644
--- a/src/components/launch/popovers/WebcamPopover.tsx
+++ b/src/components/launch/popovers/WebcamPopover.tsx
@@ -1,5 +1,3 @@
-import { getWebcamGlassRimImage } from "../../video-editor/webcamGlassRim";
-import { getWebcamCornerRadiusPx } from "../../video-editor/webcamOverlay";
import type { WebcamShape } from "../../video-editor/types";
import { getWebcamClipPath } from "../../video-editor/webcamOverlay";
import {
@@ -106,7 +104,7 @@ export function WebcamPopover({
{showWebcamControls && (
)}
diff --git a/src/components/video-editor/videoPlayback/PlaybackView.tsx b/src/components/video-editor/videoPlayback/PlaybackView.tsx
index 2d64163c..a42e9d5a 100644
--- a/src/components/video-editor/videoPlayback/PlaybackView.tsx
+++ b/src/components/video-editor/videoPlayback/PlaybackView.tsx
@@ -272,15 +272,6 @@ export function PlaybackView(input: PlaybackViewInput) {
/>
-
) : null}
diff --git a/src/components/video-editor/videoPlayback/usePlaybackWebcamLayout.ts b/src/components/video-editor/videoPlayback/usePlaybackWebcamLayout.ts
index e652b1e4..2ba7f080 100644
--- a/src/components/video-editor/videoPlayback/usePlaybackWebcamLayout.ts
+++ b/src/components/video-editor/videoPlayback/usePlaybackWebcamLayout.ts
@@ -16,7 +16,6 @@ import {
getWebcamOverlayPosition,
scaleWebcamOverlayPixels,
} from "../webcamOverlay";
-import { getWebcamGlassRimImage } from "../webcamGlassRim";
import { findPreviewClipAtTimelineTime } from "./clipPlayback";
import type { PlaybackProps } from "./playbackProps";
import type { usePlaybackState } from "./usePlaybackState";
@@ -202,15 +201,6 @@ export function usePlaybackWebcamLayout(input: usePlaybackWebcamLayoutInput) {
bubbleInner.style.overflow = "hidden";
bubbleInner.style.contain = "paint";
bubbleInner.style.clipPath = webcamClipPath;
- bubbleInner.style.setProperty(
- "--webcam-glass-rim",
- getWebcamGlassRimImage(
- scaledDimensions.width,
- scaledDimensions.height,
- scaledRadius,
- webcam?.shape,
- ),
- );
bubbleInner.style.setProperty("-webkit-clip-path", webcamClipPath);
const video = webcamVideoRef.current;
if (!video || video.readyState < 2 || !webcamMediaSynchronized) {
diff --git a/src/components/video-editor/webcamGlassRim.ts b/src/components/video-editor/webcamGlassRim.ts
deleted file mode 100644
index 37e8ead0..00000000
--- a/src/components/video-editor/webcamGlassRim.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { drawSquircleOnCanvas, getSquircleSvgPath } from "@/lib/geometry/squircle";
-import type { WebcamShape } from "./types";
-import { getWebcamShapeExponent } from "./webcamOverlay";
-
-// A translucent bevel catches light without covering the camera image. Widths
-// scale with the camera, so preview and full-resolution exports agree.
-const GLASS_STOPS = [
- [0, "rgba(255,255,255,0.8)"],
- [0.35, "rgba(255,255,255,0.24)"],
- [0.65, "rgba(15,23,42,0.16)"],
- [1, "rgba(255,255,255,0.5)"],
-] as const;
-
-export function getWebcamGlassRimWidth(width: number, height: number): number {
- return Math.max(0, Math.min(width, height)) * 0.012;
-}
-
-let cachedImage: { key: string; image: string } | undefined;
-
-export function getWebcamGlassRimImage(
- width: number,
- height: number,
- radius: number,
- shape?: WebcamShape,
-): string {
- const key = `${width}:${height}:${radius}:${shape}`;
- if (cachedImage?.key === key) return cachedImage.image;
- const path = getSquircleSvgPath({
- x: 0,
- y: 0,
- width,
- height,
- radius,
- exponent: getWebcamShapeExponent(shape),
- });
- const rim = getWebcamGlassRimWidth(width, height);
- const stops = GLASS_STOPS.map(
- ([offset, color]) => ``,
- ).join("");
- const svg = ``;
- const image = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
- cachedImage = { key, image };
- return image;
-}
-
-export function drawWebcamGlassRim(
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- radius: number,
- shape?: WebcamShape,
-): void {
- if (width <= 0 || height <= 0) return;
- ctx.save();
- drawSquircleOnCanvas(ctx, {
- x: 0,
- y: 0,
- width,
- height,
- radius,
- exponent: getWebcamShapeExponent(shape),
- });
- ctx.clip();
- const gradient = ctx.createLinearGradient(0, 0, width, height);
- for (const [offset, color] of GLASS_STOPS) gradient.addColorStop(offset, color);
- const rim = getWebcamGlassRimWidth(width, height);
- ctx.strokeStyle = gradient;
- ctx.lineWidth = rim * 2;
- ctx.stroke();
- ctx.strokeStyle = "rgba(255,255,255,0.55)";
- ctx.lineWidth = rim * 0.4;
- ctx.stroke();
- ctx.restore();
-}
diff --git a/src/components/video-editor/webcamMotionBlur.ts b/src/components/video-editor/webcamMotionBlur.ts
index e9c50bee..dfa2bd58 100644
--- a/src/components/video-editor/webcamMotionBlur.ts
+++ b/src/components/video-editor/webcamMotionBlur.ts
@@ -1,7 +1,6 @@
import { getWebcamShadowFilter, getWebcamShadowPadding } from "@/lib/exporter/shadowProfile";
import { drawSquircleOnCanvas } from "@/lib/geometry/squircle";
import type { CropRegion, WebcamShape } from "./types";
-import { drawWebcamGlassRim } from "./webcamGlassRim";
import { getWebcamCropSourceRect, getWebcamShapeExponent } from "./webcamOverlay";
export interface WebcamMotionLayout {
@@ -221,16 +220,6 @@ export class WebcamMotionBlur {
ctx.restore();
}
ctx.restore();
- ctx.save();
- ctx.scale(resolution, resolution);
- ctx.globalAlpha = 1 / this.samples.length;
- for (const sample of this.samples) {
- ctx.save();
- ctx.translate(sample.x - x, sample.y - y);
- drawWebcamGlassRim(ctx, sample.width, sample.height, sample.radius, options.shape);
- ctx.restore();
- }
- ctx.restore();
out.clearRect(0, 0, pixelWidth, pixelHeight);
out.save();
out.filter = getWebcamShadowFilter(shadowSize * resolution, shadow);
diff --git a/src/lib/exporter/modernFrameRenderer.testSupport.ts b/src/lib/exporter/modernFrameRenderer.testSupport.ts
index 6fdc4ef2..3ddd5cd8 100644
--- a/src/lib/exporter/modernFrameRenderer.testSupport.ts
+++ b/src/lib/exporter/modernFrameRenderer.testSupport.ts
@@ -13,8 +13,6 @@ export function createMockContext() {
lineTo: vi.fn(),
closePath: vi.fn(),
clip: vi.fn(),
- stroke: vi.fn(),
- createLinearGradient: vi.fn(() => ({ addColorStop: vi.fn() })),
save: vi.fn(),
restore: vi.fn(),
getImageData: vi.fn(() => ({ data: new Uint8ClampedArray(0) })),
diff --git a/tests/ui/webcam-defaults.spec.ts b/tests/ui/webcam-defaults.spec.ts
index 36f4953e..005846d8 100644
--- a/tests/ui/webcam-defaults.spec.ts
+++ b/tests/ui/webcam-defaults.spec.ts
@@ -124,7 +124,7 @@ test("new recordings default to a circle and retain the squircle option", async
await expect(mask).toHaveCSS("clip-path", "circle(50% at 50% 50%)");
});
-test("Rectangle starts at 60% width, keeps Square roundness and shows a glass rim", async ({
+test("Rectangle starts at 60% width, keeps Square roundness", async ({
page,
}) => {
await installDesktopBridge(page);
@@ -150,15 +150,12 @@ test("Rectangle starts at 60% width, keeps Square roundness and shows a glass ri
await expect(page.getByRole("slider", { name: "Webcam Width" })).toHaveValue("60");
await expect(page.getByRole("slider", { name: "Webcam Height" })).toHaveValue("40");
await expect(roundness).toHaveValue("25");
- const rim = page.locator("[data-webcam-glass-rim]");
- await expect(rim).toBeVisible();
- await expect(rim).toHaveCSS("background-image", /data:image\/svg\+xml/);
await expect(page.locator("[data-webcam-overlay]")).toHaveAttribute(
"data-webcam-ready",
"true",
);
await page.screenshot({
- path: "test-results/webcam-rectangle-glass-rim.png",
+ path: "test-results/webcam-rectangle-geometry.png",
animations: "disabled",
});
await page.getByRole("row", { name: "Square", exact: true }).click();
diff --git a/tests/ui/webcam-glass-rim.spec.ts b/tests/ui/webcam-glass-rim.spec.ts
deleted file mode 100644
index 88b1b494..00000000
--- a/tests/ui/webcam-glass-rim.spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { expect, test } from "@playwright/test";
-
-test("glass rim has the same light direction and opacity in CSS and export at every shape", async ({
- page,
-}, info) => {
- await page.goto("/tests/ui/webcam-edge.html");
- const results = await page.evaluate(async () => {
- const modulePath = "/src/components/video-editor/webcamGlassRim.ts";
- const { drawWebcamGlassRim, getWebcamGlassRimImage } = await import(
- /* @vite-ignore */ modulePath
- );
- const results = [];
- for (const shape of ["circle", "squircle", "rectangle"]) {
- const width = 240;
- const height = shape === "rectangle" ? 160 : 240;
- const radius = shape === "circle" ? 120 : height * 0.25;
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext("2d")!;
- drawWebcamGlassRim(ctx, width, height, radius, shape);
- const css = document.createElement("canvas");
- css.width = width;
- css.height = height;
- const cssCtx = css.getContext("2d")!;
- const image = new Image();
- image.src = getWebcamGlassRimImage(width, height, radius, shape).slice(5, -2);
- await image.decode();
- cssCtx.drawImage(image, 0, 0);
- const points = [
- [width / 2, 1],
- [1, height / 2],
- [width - 2, height / 2],
- [width / 2, height - 2],
- ];
- results.push({
- shape,
- edges: points.map(([x, y]) => ({
- canvas: Array.from(ctx.getImageData(x, y, 1, 1).data),
- css: Array.from(cssCtx.getImageData(x, y, 1, 1).data),
- })),
- center: Array.from(ctx.getImageData(width / 2, height / 2, 1, 1).data),
- });
- for (const output of [css, canvas]) {
- output.style.background = "#707b8e";
- output.dataset.shape = shape;
- document.body.append(output);
- }
- }
- return results;
- });
- for (const result of results) {
- expect(result.center).toEqual([0, 0, 0, 0]);
- for (const edge of result.edges) {
- expect(edge.canvas[3]).toBeGreaterThan(0);
- for (let channel = 0; channel < 4; channel++)
- expect(
- Math.abs(edge.canvas[channel] - edge.css[channel]),
- `${result.shape} channel ${channel}`,
- ).toBeLessThanOrEqual(3);
- }
- }
- await page.screenshot({ path: info.outputPath("glass-rim-css-export.png"), fullPage: true });
-});