diff options
Diffstat (limited to 'apps/web/src/services')
6 files changed, 65 insertions, 16 deletions
diff --git a/apps/web/src/services/renderer/nodes/image-node.ts b/apps/web/src/services/renderer/nodes/image-node.ts index b3a0be74..6f4f2bd8 100644 --- a/apps/web/src/services/renderer/nodes/image-node.ts +++ b/apps/web/src/services/renderer/nodes/image-node.ts @@ -84,6 +84,7 @@ export class ImageNode extends VisualNode<ImageNodeParams> { source, sourceWidth: width || renderer.width, sourceHeight: height || renderer.height, + timelineTime: time, }); } } diff --git a/apps/web/src/services/renderer/nodes/sticker-node.ts b/apps/web/src/services/renderer/nodes/sticker-node.ts index acf638eb..86f14fb4 100644 --- a/apps/web/src/services/renderer/nodes/sticker-node.ts +++ b/apps/web/src/services/renderer/nodes/sticker-node.ts @@ -62,6 +62,7 @@ export class StickerNode extends VisualNode<StickerNodeParams> { source, sourceWidth: width, sourceHeight: height, + timelineTime: time, }); } } diff --git a/apps/web/src/services/renderer/nodes/text-node.ts b/apps/web/src/services/renderer/nodes/text-node.ts index 76d78b31..0ddb4eae 100644 --- a/apps/web/src/services/renderer/nodes/text-node.ts +++ b/apps/web/src/services/renderer/nodes/text-node.ts @@ -12,6 +12,11 @@ import { getTextBackgroundRect, measureTextBlock, } from "@/lib/text/layout"; +import { + getElementLocalTime, + resolveOpacityAtTime, + resolveTransformAtTime, +} from "@/lib/animation"; function scaleFontSize({ fontSize, @@ -86,16 +91,28 @@ export class TextNode extends BaseNode<TextNodeParams> { renderer.context.save(); - const x = this.params.transform.position.x + this.params.canvasCenter.x; - const y = this.params.transform.position.y + this.params.canvasCenter.y; + const localTime = getElementLocalTime({ + timelineTime: time, + elementStartTime: this.params.startTime, + elementDuration: this.params.duration, + }); + const transform = resolveTransformAtTime({ + baseTransform: this.params.transform, + animations: this.params.animations, + localTime, + }); + const opacity = resolveOpacityAtTime({ + baseOpacity: this.params.opacity, + animations: this.params.animations, + localTime, + }); + const x = transform.position.x + this.params.canvasCenter.x; + const y = transform.position.y + this.params.canvasCenter.y; renderer.context.translate(x, y); - renderer.context.scale( - this.params.transform.scale, - this.params.transform.scale, - ); - if (this.params.transform.rotate) { - renderer.context.rotate((this.params.transform.rotate * Math.PI) / 180); + renderer.context.scale(transform.scale, transform.scale); + if (transform.rotate) { + renderer.context.rotate((transform.rotate * Math.PI) / 180); } const fontWeight = this.params.fontWeight === "bold" ? "bold" : "normal"; @@ -138,7 +155,7 @@ export class TextNode extends BaseNode<TextNodeParams> { ? this.params.blendMode : "source-over" ) as GlobalCompositeOperation; - renderer.context.globalAlpha = this.params.opacity; + renderer.context.globalAlpha = opacity; if ( this.params.background.color && diff --git a/apps/web/src/services/renderer/nodes/video-node.ts b/apps/web/src/services/renderer/nodes/video-node.ts index 8e2ddccd..9ae63661 100644 --- a/apps/web/src/services/renderer/nodes/video-node.ts +++ b/apps/web/src/services/renderer/nodes/video-node.ts @@ -16,7 +16,7 @@ export class VideoNode extends VisualNode<VideoNodeParams> { return; } - const videoTime = this.getLocalTime(time); + const videoTime = this.getSourceLocalTime(time); const frame = await videoCache.getFrameAt({ mediaId: this.params.mediaId, file: this.params.file, @@ -29,6 +29,7 @@ export class VideoNode extends VisualNode<VideoNodeParams> { source: frame.canvas, sourceWidth: frame.canvas.width, sourceHeight: frame.canvas.height, + timelineTime: time, }); } } diff --git a/apps/web/src/services/renderer/nodes/visual-node.ts b/apps/web/src/services/renderer/nodes/visual-node.ts index 7179369e..50451b4a 100644 --- a/apps/web/src/services/renderer/nodes/visual-node.ts +++ b/apps/web/src/services/renderer/nodes/visual-node.ts @@ -2,8 +2,13 @@ import type { CanvasRenderer } from "../canvas-renderer"; import { BaseNode } from "./base-node"; import type { BlendMode } from "@/types/rendering"; import type { Transform } from "@/types/timeline"; - -const VISUAL_EPSILON = 1 / 1000; +import type { ElementAnimations } from "@/types/animation"; +import { + getElementLocalTime, + resolveOpacityAtTime, + resolveTransformAtTime, +} from "@/lib/animation"; +import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants"; export interface VisualNodeParams { duration: number; @@ -11,6 +16,7 @@ export interface VisualNodeParams { trimStart: number; trimEnd: number; transform: Transform; + animations?: ElementAnimations; opacity: number; blendMode?: BlendMode; } @@ -18,14 +24,22 @@ export interface VisualNodeParams { export abstract class VisualNode< Params extends VisualNodeParams = VisualNodeParams, > extends BaseNode<Params> { - protected getLocalTime(time: number): number { + protected getSourceLocalTime(time: number): number { return time - this.params.timeOffset + this.params.trimStart; } + protected getAnimationLocalTime(time: number): number { + return getElementLocalTime({ + timelineTime: time, + elementStartTime: this.params.timeOffset, + elementDuration: this.params.duration, + }); + } + protected isInRange(time: number): boolean { - const localTime = this.getLocalTime(time); + const localTime = this.getSourceLocalTime(time); return ( - localTime >= this.params.trimStart - VISUAL_EPSILON && + localTime >= this.params.trimStart - TIME_EPSILON_SECONDS && localTime < this.params.trimStart + this.params.duration ); } @@ -35,15 +49,27 @@ export abstract class VisualNode< source, sourceWidth, sourceHeight, + timelineTime, }: { renderer: CanvasRenderer; source: CanvasImageSource; sourceWidth: number; sourceHeight: number; + timelineTime: number; }): void { renderer.context.save(); - const { transform, opacity } = this.params; + const animationLocalTime = this.getAnimationLocalTime(timelineTime); + const transform = resolveTransformAtTime({ + baseTransform: this.params.transform, + animations: this.params.animations, + localTime: animationLocalTime, + }); + const opacity = resolveOpacityAtTime({ + baseOpacity: this.params.opacity, + animations: this.params.animations, + localTime: animationLocalTime, + }); const containScale = Math.min( renderer.width / sourceWidth, renderer.height / sourceHeight, diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts index 530b2eb3..2bbcf25f 100644 --- a/apps/web/src/services/renderer/scene-builder.ts +++ b/apps/web/src/services/renderer/scene-builder.ts @@ -68,6 +68,7 @@ export function buildScene(params: BuildSceneParams) { trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, + animations: element.animations, opacity: element.opacity, blendMode: element.blendMode, }), @@ -82,6 +83,7 @@ export function buildScene(params: BuildSceneParams) { trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, + animations: element.animations, opacity: element.opacity, blendMode: element.blendMode, ...(params.isPreview && { @@ -112,6 +114,7 @@ export function buildScene(params: BuildSceneParams) { trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, + animations: element.animations, opacity: element.opacity, blendMode: element.blendMode, }), |
