From d04cd7068d10bf4625756577162fb565c9250da3 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Sat, 7 Nov 2026 06:19:11 +1100 Subject: [PATCH] Remove cancelled webcam glass border while retaining geometry and renderer fixes --- .../launch/FloatingWebcamPreviewView.tsx | 9 --- .../launch/popovers/WebcamPopover.tsx | 17 +---- .../videoPlayback/PlaybackView.tsx | 9 --- .../videoPlayback/usePlaybackWebcamLayout.ts | 10 --- src/components/video-editor/webcamGlassRim.ts | 74 ------------------- .../video-editor/webcamMotionBlur.ts | 11 --- .../modernFrameRenderer.testSupport.ts | 2 - tests/ui/webcam-defaults.spec.ts | 7 +- tests/ui/webcam-glass-rim.spec.ts | 64 ---------------- 9 files changed, 3 insertions(+), 200 deletions(-) delete mode 100644 src/components/video-editor/webcamGlassRim.ts delete mode 100644 tests/ui/webcam-glass-rim.spec.ts 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 = `${stops}`; - 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 }); -});