summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-01 15:15:12 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-01 15:15:41 +0200
commit8a1d37cf3e8d06dd7af75b5d6f9181afcc797f84 (patch)
tree5f0e874abe3ecbdf8e5e2e3243f3743609382490
parente579ae120290329fd697b50ea9138ffccb65a805 (diff)
downloadopencut-8a1d37cf3e8d06dd7af75b5d6f9181afcc797f84.tar.gz
fix: align background blur with effect blur intensity scale
-rw-r--r--apps/web/src/constants/project-constants.ts8
-rw-r--r--apps/web/src/lib/effects/definitions/blur.ts21
-rw-r--r--apps/web/src/services/renderer/nodes/blur-background-node.ts6
-rw-r--r--apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts92
-rw-r--r--apps/web/src/services/storage/migrations/index.ts4
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts66
-rw-r--r--apps/web/src/services/storage/migrations/v20-to-v21.ts16
7 files changed, 193 insertions, 20 deletions
diff --git a/apps/web/src/constants/project-constants.ts b/apps/web/src/constants/project-constants.ts
index 24e75381..96e178dc 100644
--- a/apps/web/src/constants/project-constants.ts
+++ b/apps/web/src/constants/project-constants.ts
@@ -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";
diff --git a/apps/web/src/lib/effects/definitions/blur.ts b/apps/web/src/lib/effects/definitions/blur.ts
index 3bfd5e3c..b464db3a 100644
--- a/apps/web/src/lib/effects/definitions/blur.ts
+++ b/apps/web/src/lib/effects/definitions/blur.ts
@@ -7,11 +7,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,
@@ -59,8 +54,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 = {
{
shader: GAUSSIAN_BLUR_SHADER,
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 = {
{
shader: GAUSSIAN_BLUR_SHADER,
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 }),
});
},
},
diff --git a/apps/web/src/services/renderer/nodes/blur-background-node.ts b/apps/web/src/services/renderer/nodes/blur-background-node.ts
index f790ebb4..c67a5113 100644
--- a/apps/web/src/services/renderer/nodes/blur-background-node.ts
+++ b/apps/web/src/services/renderer/nodes/blur-background-node.ts
@@ -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 = gpuRenderer.applyEffect({
source: offscreen as CanvasImageSource,
diff --git a/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts b/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts
new file mode 100644
index 00000000..af2124c8
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/__tests__/v20-to-v21.test.ts
@@ -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");
+ });
+});
diff --git a/apps/web/src/services/storage/migrations/index.ts b/apps/web/src/services/storage/migrations/index.ts
index d278263b..ea596435 100644
--- a/apps/web/src/services/storage/migrations/index.ts
+++ b/apps/web/src/services/storage/migrations/index.ts
@@ -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(),
];
diff --git a/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts
new file mode 100644
index 00000000..63ad24de
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/transformers/v20-to-v21.ts
@@ -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,
+ },
+ },
+ };
+}
diff --git a/apps/web/src/services/storage/migrations/v20-to-v21.ts b/apps/web/src/services/storage/migrations/v20-to-v21.ts
new file mode 100644
index 00000000..f04ad35f
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/v20-to-v21.ts
@@ -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 });
+ }
+}