Merge pull request #839 from webadderallorg/codex/editor-visual-polish

Polish editor preview and motion blur
This commit is contained in:
webadderall
2026-08-27 20:27:56 +10:00
committed by GitHub
7 changed files with 91 additions and 60 deletions
+2 -2
View File
@@ -5573,7 +5573,7 @@ export default function VideoEditor() {
zoomInEasing={zoomInEasing}
zoomOutEasing={zoomOutEasing}
connectedZoomEasing={connectedZoomEasing}
borderRadius={borderRadius}
borderRadius={0}
padding={padding}
frame={frame}
cropRegion={cropRegion}
@@ -5911,7 +5911,7 @@ export default function VideoEditor() {
)}
</div>
<div
className="flex items-center justify-self-end pr-3"
className="flex items-center justify-self-end"
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
>
<Popover open={presetPopoverOpen} onOpenChange={setPresetPopoverOpen}>
@@ -1,4 +1,8 @@
import { DEFAULT_ZOOM_IN_DURATION_MS, DEFAULT_ZOOM_OUT_DURATION_MS } from "./types";
import {
DEFAULT_CURSOR_MOTION_BLUR,
DEFAULT_ZOOM_IN_DURATION_MS,
DEFAULT_ZOOM_OUT_DURATION_MS,
} from "./types";
export type CursorMotionPresetId = "focused" | "smooth";
@@ -35,7 +39,7 @@ const SHARED_CURSOR_PRESET_VALUES = {
cursorSize: 2.5,
cursorSmoothing: 0.67,
cursorSpringMassMultiplier: 1.29,
cursorMotionBlur: 0.4,
cursorMotionBlur: DEFAULT_CURSOR_MOTION_BLUR,
cursorClickBounce: 3.5,
cursorClickBounceDuration: 350,
} as const;
+3 -3
View File
@@ -147,7 +147,7 @@ export interface WebcamOverlaySettings {
export const DEFAULT_CURSOR_SIZE = 3.0;
export const DEFAULT_CURSOR_SMOOTHING = 0.67;
export const DEFAULT_CURSOR_MOTION_BLUR = 0.4;
export const DEFAULT_CURSOR_MOTION_BLUR = 0.6;
export const DEFAULT_CURSOR_CLICK_BOUNCE = 2.5;
export const DEFAULT_CURSOR_CLICK_BOUNCE_DURATION = 350;
export const DEFAULT_CURSOR_SWAY = 0.4;
@@ -435,11 +435,11 @@ export interface AnnotationTextStyle {
}
function getDefaultAnnotationFontFamily() {
return '"SF Pro Display", "SF Pro Text", Helvetica, sans-serif';
return '"SF Pro Display", "SF Pro Text", "Helvetica Neue", sans-serif';
}
export function getDefaultCaptionFontFamily() {
return '"SF Pro Text", "SF Pro Display", Helvetica, sans-serif';
return '"SF Pro Text", "SF Pro Display", "Helvetica Neue", sans-serif';
}
export interface AnnotationRegion {
@@ -27,6 +27,7 @@ import {
stepSpringValue,
} from "./motionSmoothing";
import { cursorSetAssets, getCursorStyleSizeMultiplier } from "./uploadedCursorAssets";
import { computeDirectionalMotionBlur } from "./zoomTransform";
type CursorAssetKey = NonNullable<CursorTelemetryPoint["cursorType"]>;
type StatefulCursorStyle = Extract<CursorStyle, "macos" | "tahoe" | "tahoe-inverted">;
@@ -112,7 +113,9 @@ export interface CursorRenderConfig {
}
const MIN_CURSOR_VIEWPORT_SCALE = 0;
const CURSOR_MOTION_BLUR_BASE_MULTIPLIER = 0.08;
// Preserve the cursor's stronger visual response while sharing the camera's
// frame-rate normalization, kernel, and directional offset formula.
const CURSOR_DIRECTIONAL_BLUR_STRENGTH = 4.8;
const CURSOR_TIME_DISCONTINUITY_MS = 100;
const CURSOR_SWAY_SMOOTHING_MULTIPLIER = 0.7;
const CURSOR_SWAY_SMOOTHING_OFFSET = 0.18;
@@ -1569,17 +1572,16 @@ export class PixiCursorOverlay {
const deltaMs = Math.max(1, timeMs - this.lastRenderedTimeMs);
const dx = px - this.lastRenderedPoint.px;
const dy = py - this.lastRenderedPoint.py;
const velocityScale =
(1000 / deltaMs) * this.config.motionBlur * CURSOR_MOTION_BLUR_BASE_MULTIPLIER;
const velocity = {
x: dx * velocityScale,
y: dy * velocityScale,
};
const magnitude = Math.hypot(velocity.x, velocity.y);
const blur = computeDirectionalMotionBlur(
{ x: dx, y: dy },
this.config.motionBlur,
deltaMs / 1000,
CURSOR_DIRECTIONAL_BLUR_STRENGTH,
);
this.cursorMotionBlurFilter.velocity = magnitude > 0.05 ? velocity : { x: 0, y: 0 };
this.cursorMotionBlurFilter.kernelSize = magnitude > 3 ? 9 : magnitude > 1 ? 7 : 5;
this.cursorMotionBlurFilter.offset = magnitude > 0.5 ? -0.25 : 0;
this.cursorMotionBlurFilter.velocity = blur.velocity;
this.cursorMotionBlurFilter.kernelSize = blur.kernelSize;
this.cursorMotionBlurFilter.offset = blur.offset;
}
reset(): void {
@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { applyZoomTransform, computeZoomTransform, createMotionBlurState } from "./zoomTransform";
import {
applyZoomTransform,
computeDirectionalMotionBlur,
computeZoomTransform,
createMotionBlurState,
} from "./zoomTransform";
function createStubContainer() {
return {
@@ -8,6 +13,18 @@ function createStubContainer() {
};
}
describe("computeDirectionalMotionBlur", () => {
it("normalizes cursor and camera motion to the same 60fps baseline", () => {
const at60Fps = computeDirectionalMotionBlur({ x: 12, y: 0 }, 0.5, 1 / 60);
const at30Fps = computeDirectionalMotionBlur({ x: 12, y: 0 }, 0.5, 1 / 30);
expect(at60Fps.velocity).toEqual({ x: 6, y: 0 });
expect(at30Fps.velocity).toEqual({ x: 3, y: 0 });
expect(at60Fps.kernelSize).toBe(13);
expect(at60Fps.offset).toBeCloseTo(-6 / 32, 6);
});
});
describe("applyZoomTransform motion blur routing", () => {
it("keeps pure translation on the directional blur path", () => {
const motionBlurState = createMotionBlurState();
@@ -7,6 +7,13 @@ const MIN_DIRECTIONAL_BLUR_MAGNITUDE = 0.01;
const DIRECTIONAL_BLUR_KERNEL_SIZE = 13;
const DIRECTIONAL_BLUR_OFFSET_DIVISOR = 32;
export interface DirectionalMotionBlur {
velocity: { x: number; y: number };
magnitude: number;
kernelSize: number;
offset: number;
}
export interface MotionBlurState {
lastFrameTimeMs: number;
prevCamX: number;
@@ -247,25 +254,40 @@ function resolveFpsScale(deltaSeconds: number) {
return fps / 60;
}
function resolveBlurChannels(
export function computeDirectionalMotionBlur(
delta: { x: number; y: number },
motionBlurAmount: number,
deltaSeconds: number,
strengthMultiplier = 1,
): DirectionalMotionBlur {
const scale = motionBlurAmount * resolveFpsScale(deltaSeconds) * strengthMultiplier;
const velocity = {
x: delta.x * scale,
y: delta.y * scale,
};
const magnitude = Math.hypot(velocity.x, velocity.y);
const isActive = magnitude >= MIN_DIRECTIONAL_BLUR_MAGNITUDE;
return {
velocity: isActive ? velocity : createZeroPoint(),
magnitude: isActive ? magnitude : 0,
kernelSize: DIRECTIONAL_BLUR_KERNEL_SIZE,
offset: isActive ? -magnitude / DIRECTIONAL_BLUR_OFFSET_DIVISOR : 0,
};
}
function resolveZoomBlurStrength(
motionBlurAmount: number,
motionBlurTuning: ZoomMotionBlurTuning,
deltaSeconds: number,
) {
const fpsScale = resolveFpsScale(deltaSeconds);
return {
motion:
motionBlurAmount *
fpsScale *
(motionBlurTuning.maxDirectionalBlurPx /
DEFAULT_ZOOM_MOTION_BLUR_TUNING.maxDirectionalBlurPx),
zoom:
motionBlurAmount *
fpsScale *
(motionBlurTuning.maxRadialBlurStrength /
DEFAULT_ZOOM_MOTION_BLUR_TUNING.maxRadialBlurStrength),
};
return (
motionBlurAmount *
fpsScale *
(motionBlurTuning.maxRadialBlurStrength /
DEFAULT_ZOOM_MOTION_BLUR_TUNING.maxRadialBlurStrength)
);
}
function computeMoveDelta(previousQuad: TransformQuad, currentQuad: TransformQuad) {
@@ -340,40 +362,29 @@ function analyzeCameraStep({
motionBlurTuning: ZoomMotionBlurTuning;
deltaSeconds: number;
}): CameraStepAnalysis {
const mode = classifyMotionMode(
previousQuad,
currentQuad,
motionBlurTuning,
deltaSeconds,
);
const mode = classifyMotionMode(previousQuad, currentQuad, motionBlurTuning, deltaSeconds);
const moveDelta = computeMoveDelta(previousQuad, currentQuad);
const blurChannels = resolveBlurChannels(
const zoomBlurStrength = resolveZoomBlurStrength(
motionBlurAmount,
motionBlurTuning,
deltaSeconds,
);
const moveBlurVelocity = {
x: moveDelta.x * blurChannels.motion,
y: moveDelta.y * blurChannels.motion,
};
const moveBlurMagnitude = Math.hypot(moveBlurVelocity.x, moveBlurVelocity.y);
const directionalBlur = computeDirectionalMotionBlur(
moveDelta,
motionBlurAmount,
deltaSeconds,
motionBlurTuning.maxDirectionalBlurPx /
DEFAULT_ZOOM_MOTION_BLUR_TUNING.maxDirectionalBlurPx,
);
return {
mode,
moveVelocity: moveDelta,
moveBlurVelocity:
mode === "move" && moveBlurMagnitude >= MIN_DIRECTIONAL_BLUR_MAGNITUDE
? moveBlurVelocity
: createZeroPoint(),
moveBlurOffset:
mode === "move" && moveBlurMagnitude >= MIN_DIRECTIONAL_BLUR_MAGNITUDE
? -moveBlurMagnitude / DIRECTIONAL_BLUR_OFFSET_DIVISOR
: 0,
moveBlurVelocity: mode === "move" ? directionalBlur.velocity : createZeroPoint(),
moveBlurOffset: mode === "move" ? directionalBlur.offset : 0,
zoomCenter: inferZoomCenterFromQuads(previousQuad, currentQuad, stageSize),
zoomStrength:
mode === "zoom"
? computeZoomStrength(previousQuad, currentQuad) * blurChannels.zoom
: 0,
mode === "zoom" ? computeZoomStrength(previousQuad, currentQuad) * zoomBlurStrength : 0,
};
}
+1 -4
View File
@@ -29,7 +29,7 @@
}
:root {
--app-font-sans: "SF Pro Display", "SF Pro Text", Helvetica, sans-serif;
--app-font-sans: "SF Pro Display", "SF Pro Text", "Helvetica Neue", sans-serif;
--brand-accent: #2563eb;
--brand-accent-rgb: 37, 99, 235;
--background: 0 0% 100%;
@@ -268,6 +268,3 @@
transform: translateX(270%);
}
}