Make webcam rounding and shadows more pronounced

This commit is contained in:
young
2026-09-01 20:04:06 +10:00
parent d127f9f8ee
commit 0f08e3ef81
6 changed files with 40 additions and 24 deletions
+3 -16
View File
@@ -12,10 +12,7 @@ import {
useState,
} from "react";
import { getAssetPath, getRenderableAssetUrl, getRenderableVideoUrl } from "@/lib/assetPath";
import {
getWebcamShadowStrength,
WEBCAM_SHADOW_LAYER_PROFILES,
} from "@/lib/exporter/shadowProfile";
import { getWebcamShadowFilter } from "@/lib/exporter/shadowProfile";
import {
clampMediaTimeToDuration,
enablePitchPreservingPlayback,
@@ -799,8 +796,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
const webcamCorner = webcam?.corner ?? "bottom-right";
const webcamRoundness = webcam?.roundness ?? DEFAULT_WEBCAM_ROUNDNESS;
const webcamShadow = webcam?.shadow ?? DEFAULT_WEBCAM_SHADOW;
const webcamShadowStrength = getWebcamShadowStrength(webcamShadow);
const webcamShadowProfile = WEBCAM_SHADOW_LAYER_PROFILES[0];
const webcamTimeOffsetMs = webcam?.timeOffsetMs;
const webcamCropRegion = webcam?.cropRegion;
const webcamMirror = webcam?.mirror ?? false;
@@ -891,15 +886,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
radius: scaledRadius,
});
const shadowSize = Math.min(scaledDimensions.width, scaledDimensions.height);
const shadowOffset =
shadowSize * webcamShadowProfile.offsetScale * webcamShadowStrength;
const shadowBlur =
shadowSize * webcamShadowProfile.blurScale * webcamShadowStrength;
const shadowAlpha = webcamShadowProfile.alphaScale * webcamShadowStrength;
bubble.style.filter =
webcamShadowStrength > 0
? `drop-shadow(0 ${shadowOffset}px ${shadowBlur}px rgba(0, 0, 0, ${shadowAlpha}))`
: "none";
bubble.style.filter = getWebcamShadowFilter(shadowSize, webcamShadow);
bubble.style.borderRadius = "0px";
bubble.style.boxShadow = "none";
@@ -918,7 +905,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
webcamPositionX,
webcamPositionY,
webcamReactToZoom,
webcamShadowStrength,
webcamShadow,
webcamHeight,
webcamVideoPath,
webcamWidth,
@@ -12,9 +12,10 @@ import {
} from "./webcamOverlay";
describe("webcam roundness", () => {
it("uses a percentage of the maximum radius", () => {
it("uses a stronger response curve while preserving the endpoints", () => {
expect(getWebcamCornerRadiusPx(0, 640, 360)).toBe(0);
expect(getWebcamCornerRadiusPx(50, 640, 360)).toBe(90);
expect(getWebcamCornerRadiusPx(25, 640, 360)).toBe(90);
expect(getWebcamCornerRadiusPx(50, 640, 360)).toBeCloseTo(127.28, 1);
expect(getWebcamCornerRadiusPx(100, 640, 360)).toBe(180);
});
+2 -1
View File
@@ -20,7 +20,8 @@ export function getWebcamCornerRadiusPx(
height: number,
): number {
const safeRoundness = Number.isFinite(roundnessPercent) ? clamp(roundnessPercent, 0, 100) : 0;
return (Math.max(0, Math.min(width, height)) / 2) * (safeRoundness / 100);
const strengthenedRoundness = Math.sqrt(safeRoundness / 100);
return (Math.max(0, Math.min(width, height)) / 2) * strengthenedRoundness;
}
export function convertLegacyWebcamRadiusToRoundness(
+2 -3
View File
@@ -59,7 +59,7 @@ import {
scaleWebcamOverlayPixels,
} from "@/components/video-editor/webcamOverlay";
import { getAssetPath, getExportableVideoUrl, getRenderableAssetUrl } from "@/lib/assetPath";
import { getWebcamShadowStrength } from "@/lib/exporter/shadowProfile";
import { getWebcamShadowFilter } from "@/lib/exporter/shadowProfile";
import { drawSquircleOnCanvas, drawSquircleOnGraphics } from "@/lib/geometry/squircle";
import {
clampMediaTimeToDuration,
@@ -2166,10 +2166,9 @@ export class FrameRenderer {
bubbleCtx.restore();
if ((webcam.shadow ?? 0) > 0) {
const shadow = getWebcamShadowStrength(webcam.shadow);
const shadowSize = Math.min(dimensions.width, dimensions.height);
ctx.save();
ctx.filter = `drop-shadow(0 ${Math.round(shadowSize * 0.06)}px ${Math.round(shadowSize * 0.22)}px rgba(0,0,0,${shadow}))`;
ctx.filter = getWebcamShadowFilter(shadowSize, webcam.shadow);
ctx.drawImage(bubbleCanvas, x, y, dimensions.width, dimensions.height);
ctx.restore();
return;
+14 -1
View File
@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { getWebcamShadowStrength } from "./shadowProfile";
import {
getWebcamShadowFilter,
getWebcamShadowStrength,
WEBCAM_SHADOW_LAYER_PROFILES,
} from "./shadowProfile";
describe("getWebcamShadowStrength", () => {
it("doubles the webcam shadow response and clamps it", () => {
@@ -8,4 +12,13 @@ describe("getWebcamShadowStrength", () => {
expect(getWebcamShadowStrength(0.5)).toBe(1);
expect(getWebcamShadowStrength(1)).toBe(1);
});
it("adds a tighter contact shadow so strength also increases density", () => {
expect(WEBCAM_SHADOW_LAYER_PROFILES).toHaveLength(2);
expect(getWebcamShadowFilter(100, 0)).toBe("none");
const filter = getWebcamShadowFilter(100, 0.5);
expect(filter.match(/drop-shadow/g)).toHaveLength(2);
expect(filter).toContain("rgba(0, 0, 0, 0.9)");
expect(filter).toContain("rgba(0, 0, 0, 0.8)");
});
});
+16 -1
View File
@@ -11,13 +11,28 @@ export const VIDEO_SHADOW_LAYER_PROFILES: ReadonlyArray<ShadowLayerProfile> = Ob
]);
export const WEBCAM_SHADOW_LAYER_PROFILES: ReadonlyArray<ShadowLayerProfile> = Object.freeze([
{ offsetScale: 0.06, alphaScale: 1, blurScale: 0.22 },
{ offsetScale: 0.06, alphaScale: 0.9, blurScale: 0.22 },
{ offsetScale: 0.025, alphaScale: 0.8, blurScale: 0.07 },
]);
export function getWebcamShadowStrength(value: number): number {
return Math.min(1, Math.max(0, Number.isFinite(value) ? value * 2 : 0));
}
export function getWebcamShadowFilter(shadowSize: number, value: number): string {
const strength = getWebcamShadowStrength(value);
if (strength <= 0) {
return "none";
}
return WEBCAM_SHADOW_LAYER_PROFILES.map((profile) => {
const offset = Math.max(0, shadowSize) * profile.offsetScale * strength;
const blur = Math.max(0, shadowSize) * profile.blurScale * strength;
const alpha = Math.min(1, profile.alphaScale * strength);
return `drop-shadow(0 ${offset}px ${blur}px rgba(0, 0, 0, ${alpha}))`;
}).join(" ");
}
export function getShadowFilterPadding(blur: number, offsetY: number): number {
return Math.ceil(Math.max(0, blur * 2 + Math.abs(offsetY)));
}