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 54934402..03107373 100644 --- a/apps/web/src/lib/effects/definitions/blur.ts +++ b/apps/web/src/lib/effects/definitions/blur.ts @@ -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): 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 }), }); }, }, 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 7f4d06fc..0518dbc2 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 { ); 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, 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; + const background = settings.background as Record; + 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; + const background = settings.background as Record; + 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; + 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 { + 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 }); + } +}