fix: align background blur with effect blur intensity scale

This commit is contained in:
Maze Winther 2026-04-01 15:15:12 +02:00
parent b048b739bd
commit 301e89df89
7 changed files with 193 additions and 20 deletions

View File

@ -16,12 +16,12 @@ export const FPS_PRESETS = [
] as const;
export const BLUR_INTENSITY_PRESETS: { label: string; value: number }[] = [
{ label: "Light", value: 10 },
{ label: "Medium", value: 50 },
{ label: "Heavy", value: 100 },
{ label: "Light", value: 50 },
{ label: "Medium", value: 250 },
{ label: "Heavy", value: 500 },
] as const;
export const DEFAULT_CANVAS_SIZE: TCanvasSize = { width: 1920, height: 1080 };
export const DEFAULT_FPS = 30;
export const DEFAULT_BLUR_INTENSITY = 10;
export const DEFAULT_BLUR_INTENSITY = 50;
export const DEFAULT_COLOR = "#000000";

View File

@ -6,11 +6,6 @@ const MAX_STEP = 4;
const MAX_EFFECTIVE_SIGMA = MAX_SINGLE_PASS_SIGMA * MAX_STEP;
const MAX_ITERATIONS = 8;
/**
* Builds multi-pass gaussian blur passes for a given sigma.
* Shared by the blur effect and background blur they each
* compute their own sigma from their own intensity scale.
*/
export function buildGaussianBlurPasses({
sigmaX,
sigmaY,
@ -58,8 +53,10 @@ export function buildGaussianBlurPasses({
return passes;
}
function intensityToSigma(intensity: number, resolution: number, reference: number): number {
return (intensity / 5) * (resolution / reference);
export const INTENSITY_TO_SIGMA_DIVISOR = 5;
export function intensityToSigma({ intensity, resolution, reference }: { intensity: number; resolution: number; reference: number }): number {
return (intensity / INTENSITY_TO_SIGMA_DIVISOR) * (resolution / reference);
}
function parseIntensity(effectParams: Record<string, unknown>): number {
@ -88,7 +85,7 @@ export const blurEffectDefinition: EffectDefinition = {
{
fragmentShader: blurFragmentShader,
uniforms: ({ effectParams, width }) => ({
u_sigma: Math.max(intensityToSigma(parseIntensity(effectParams), width, 1920), 0.001),
u_sigma: Math.max(intensityToSigma({ intensity: parseIntensity(effectParams), resolution: width, reference: 1920 }), 0.001),
u_step: 1,
u_direction: [1, 0],
}),
@ -96,7 +93,7 @@ export const blurEffectDefinition: EffectDefinition = {
{
fragmentShader: blurFragmentShader,
uniforms: ({ effectParams, height }) => ({
u_sigma: Math.max(intensityToSigma(parseIntensity(effectParams), height, 1080), 0.001),
u_sigma: Math.max(intensityToSigma({ intensity: parseIntensity(effectParams), resolution: height, reference: 1080 }), 0.001),
u_step: 1,
u_direction: [0, 1],
}),
@ -104,9 +101,9 @@ export const blurEffectDefinition: EffectDefinition = {
],
buildPasses: ({ effectParams, width, height }) => {
const intensity = parseIntensity(effectParams);
return buildGaussianBlurPasses({
sigmaX: intensityToSigma(intensity, width, 1920),
sigmaY: intensityToSigma(intensity, height, 1080),
return buildGaussianBlurPasses({
sigmaX: intensityToSigma({ intensity, resolution: width, reference: 1920 }),
sigmaY: intensityToSigma({ intensity, resolution: height, reference: 1080 }),
});
},
},

View File

@ -1,5 +1,5 @@
import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants";
import { buildGaussianBlurPasses } from "@/lib/effects/definitions/blur";
import { buildGaussianBlurPasses, intensityToSigma } from "@/lib/effects/definitions/blur";
import { getSourceTimeAtClipTime } from "@/lib/retime";
import { videoCache } from "@/services/video-cache/service";
import type { RetimeConfig } from "@/lib/timeline";
@ -128,8 +128,8 @@ export class BlurBackgroundNode extends BaseNode<BlurBackgroundNodeParams> {
);
const passes = buildGaussianBlurPasses({
sigmaX: this.params.blurIntensity * (renderer.width / 1920),
sigmaY: this.params.blurIntensity * (renderer.height / 1080),
sigmaX: intensityToSigma({ intensity: this.params.blurIntensity, resolution: renderer.width, reference: 1920 }),
sigmaY: intensityToSigma({ intensity: this.params.blurIntensity, resolution: renderer.height, reference: 1080 }),
});
const effectResult = webglEffectRenderer.applyEffect({
source: offscreen as CanvasImageSource,

View File

@ -0,0 +1,92 @@
import { describe, expect, test } from "bun:test";
import { DEFAULT_BLUR_INTENSITY } from "@/constants/project-constants";
import { transformProjectV20ToV21 } from "../transformers/v20-to-v21";
describe("V20 to V21 Migration", () => {
test("multiplies blur background intensity by 5", () => {
const result = transformProjectV20ToV21({
project: {
id: "project-v20-blur",
version: 20,
metadata: {
id: "project-v20-blur",
name: "Project",
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
},
settings: {
fps: 30,
canvasSize: { width: 1920, height: 1080 },
background: { type: "blur", blurIntensity: 100 },
},
currentSceneId: "scene-main",
scenes: [],
},
});
expect(result.skipped).toBe(false);
expect(result.project.version).toBe(21);
const settings = result.project.settings as Record<string, unknown>;
const background = settings.background as Record<string, unknown>;
expect(background.blurIntensity).toBe(500);
});
test("uses default blur intensity when blur type but intensity missing", () => {
const result = transformProjectV20ToV21({
project: {
id: "project-v20-blur-no-intensity",
version: 20,
settings: {
background: { type: "blur" },
},
scenes: [],
},
});
expect(result.skipped).toBe(false);
const settings = result.project.settings as Record<string, unknown>;
const background = settings.background as Record<string, unknown>;
expect(background.blurIntensity).toBe(DEFAULT_BLUR_INTENSITY);
});
test("leaves color background unchanged", () => {
const result = transformProjectV20ToV21({
project: {
id: "project-v20-color",
version: 20,
settings: {
background: { type: "color", color: "#000000" },
},
scenes: [],
},
});
expect(result.skipped).toBe(false);
const settings = result.project.settings as Record<string, unknown>;
expect(settings.background).toEqual({ type: "color", color: "#000000" });
});
test("skips projects already on v21", () => {
const result = transformProjectV20ToV21({
project: {
id: "project-v21",
version: 21,
},
});
expect(result.skipped).toBe(true);
expect(result.reason).toBe("already v21");
});
test("skips projects not on v20", () => {
const result = transformProjectV20ToV21({
project: {
id: "project-v19",
version: 19,
},
});
expect(result.skipped).toBe(true);
expect(result.reason).toBe("not v20");
});
});

View File

@ -19,10 +19,11 @@ import { V16toV17Migration } from "./v16-to-v17";
import { V17toV18Migration } from "./v17-to-v18";
import { V18toV19Migration } from "./v18-to-v19";
import { V19toV20Migration } from "./v19-to-v20";
import { V20toV21Migration } from "./v20-to-v21";
export { runStorageMigrations } from "./runner";
export type { MigrationProgress } from "./runner";
export const CURRENT_PROJECT_VERSION = 20;
export const CURRENT_PROJECT_VERSION = 21;
export const migrations = [
new V0toV1Migration(),
@ -45,4 +46,5 @@ export const migrations = [
new V17toV18Migration(),
new V18toV19Migration(),
new V19toV20Migration(),
new V20toV21Migration(),
];

View File

@ -0,0 +1,66 @@
import { DEFAULT_BLUR_INTENSITY } from "@/constants/project-constants";
import { INTENSITY_TO_SIGMA_DIVISOR } from "@/lib/effects/definitions/blur";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
export function transformProjectV20ToV21({
project,
}: {
project: ProjectRecord;
}): MigrationResult<ProjectRecord> {
if (!getProjectId({ project })) {
return { project, skipped: true, reason: "no project id" };
}
const version = project.version;
if (typeof version !== "number") {
return { project, skipped: true, reason: "invalid version" };
}
if (version >= 21) {
return { project, skipped: true, reason: "already v21" };
}
if (version !== 20) {
return { project, skipped: true, reason: "not v20" };
}
return {
project: {
...migrateBackgroundBlurScale({ project }),
version: 21,
},
skipped: false,
};
}
function migrateBackgroundBlurScale({
project,
}: {
project: ProjectRecord;
}): ProjectRecord {
if (!isRecord(project.settings)) {
return project;
}
const settings = { ...project.settings };
const background = settings.background;
if (!isRecord(background) || background.type !== "blur") {
return { ...project, settings };
}
const raw = background.blurIntensity;
const blurIntensity =
typeof raw === "number" && Number.isFinite(raw)
? raw * INTENSITY_TO_SIGMA_DIVISOR
: DEFAULT_BLUR_INTENSITY;
return {
...project,
settings: {
...settings,
background: {
...background,
blurIntensity,
},
},
};
}

View File

@ -0,0 +1,16 @@
import { StorageMigration } from "./base";
import type { ProjectRecord } from "./transformers/types";
import { transformProjectV20ToV21 } from "./transformers/v20-to-v21";
export class V20toV21Migration extends StorageMigration {
from = 20;
to = 21;
async transform(project: ProjectRecord): Promise<{
project: ProjectRecord;
skipped: boolean;
reason?: string;
}> {
return transformProjectV20ToV21({ project });
}
}