summaryrefslogtreecommitdiff
path: root/apps/web/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/services')
-rw-r--r--apps/web/src/services/renderer/nodes/image-node.ts71
-rw-r--r--apps/web/src/services/renderer/nodes/sticker-node.ts53
-rw-r--r--apps/web/src/services/renderer/nodes/video-node.ts67
-rw-r--r--apps/web/src/services/renderer/nodes/visual-node.ts67
-rw-r--r--apps/web/src/services/renderer/scene-builder.ts4
5 files changed, 102 insertions, 160 deletions
diff --git a/apps/web/src/services/renderer/nodes/image-node.ts b/apps/web/src/services/renderer/nodes/image-node.ts
index 5405b60b..49e84ebe 100644
--- a/apps/web/src/services/renderer/nodes/image-node.ts
+++ b/apps/web/src/services/renderer/nodes/image-node.ts
@@ -1,22 +1,11 @@
import type { CanvasRenderer } from "../canvas-renderer";
-import { BaseNode } from "./base-node";
+import { VisualNode, type VisualNodeParams } from "./visual-node";
-const IMAGE_EPSILON = 1 / 1000;
-
-export interface ImageNodeParams {
+export interface ImageNodeParams extends VisualNodeParams {
url: string;
- duration: number;
- timeOffset: number;
- trimStart: number;
- trimEnd: number;
- x?: number;
- y?: number;
- width?: number;
- height?: number;
- opacity?: number;
}
-export class ImageNode extends BaseNode<ImageNodeParams> {
+export class ImageNode extends VisualNode<ImageNodeParams> {
private image?: HTMLImageElement;
private readyPromise: Promise<void>;
@@ -36,18 +25,6 @@ export class ImageNode extends BaseNode<ImageNodeParams> {
});
}
- private getImageTime(time: number) {
- return time - this.params.timeOffset + this.params.trimStart;
- }
-
- private isInRange(time: number) {
- const imageTime = this.getImageTime(time);
- return (
- imageTime >= this.params.trimStart - IMAGE_EPSILON &&
- imageTime < this.params.trimStart + this.params.duration
- );
- }
-
async render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {
await super.render({ renderer, time });
@@ -61,40 +38,14 @@ export class ImageNode extends BaseNode<ImageNodeParams> {
return;
}
- renderer.context.save();
+ const mediaW = this.image.naturalWidth || renderer.width;
+ const mediaH = this.image.naturalHeight || renderer.height;
- if (this.params.opacity !== undefined) {
- renderer.context.globalAlpha = this.params.opacity;
- }
-
- if (
- this.params.x !== undefined &&
- this.params.y !== undefined &&
- this.params.width !== undefined &&
- this.params.height !== undefined
- ) {
- renderer.context.drawImage(
- this.image,
- this.params.x,
- this.params.y,
- this.params.width,
- this.params.height,
- );
- } else {
- const mediaW = this.image.naturalWidth || renderer.width;
- const mediaH = this.image.naturalHeight || renderer.height;
- const containScale = Math.min(
- renderer.width / mediaW,
- renderer.height / mediaH,
- );
- const drawW = mediaW * containScale;
- const drawH = mediaH * containScale;
- const drawX = (renderer.width - drawW) / 2;
- const drawY = (renderer.height - drawH) / 2;
-
- renderer.context.drawImage(this.image, drawX, drawY, drawW, drawH);
- }
-
- renderer.context.restore();
+ this.renderVisual({
+ renderer,
+ source: this.image,
+ sourceWidth: mediaW,
+ sourceHeight: mediaH,
+ });
}
}
diff --git a/apps/web/src/services/renderer/nodes/sticker-node.ts b/apps/web/src/services/renderer/nodes/sticker-node.ts
index 5af13a6f..9842320e 100644
--- a/apps/web/src/services/renderer/nodes/sticker-node.ts
+++ b/apps/web/src/services/renderer/nodes/sticker-node.ts
@@ -1,21 +1,12 @@
import type { CanvasRenderer } from "../canvas-renderer";
-import { BaseNode, type BaseNodeParams } from "./base-node";
-import type { Transform } from "@/types/timeline";
+import { VisualNode, type VisualNodeParams } from "./visual-node";
-const STICKER_EPSILON = 1 / 1000;
-
-export type StickerNodeParams = BaseNodeParams & {
+export interface StickerNodeParams extends VisualNodeParams {
iconName: string;
- duration: number;
- timeOffset: number;
- trimStart: number;
- trimEnd: number;
- transform: Transform;
- opacity: number;
color?: string;
-};
+}
-export class StickerNode extends BaseNode<StickerNodeParams> {
+export class StickerNode extends VisualNode<StickerNodeParams> {
private image?: HTMLImageElement;
private readyPromise: Promise<void>;
@@ -40,18 +31,6 @@ export class StickerNode extends BaseNode<StickerNodeParams> {
});
}
- private getStickerTime(time: number) {
- return time - this.params.timeOffset + this.params.trimStart;
- }
-
- private isInRange(time: number) {
- const stickerTime = this.getStickerTime(time);
- return (
- stickerTime >= this.params.trimStart - STICKER_EPSILON &&
- stickerTime < this.params.trimStart + this.params.duration
- );
- }
-
async render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {
await super.render({ renderer, time });
@@ -65,23 +44,11 @@ export class StickerNode extends BaseNode<StickerNodeParams> {
return;
}
- const { transform, opacity } = this.params;
- const size = 200 * transform.scale;
- const x = renderer.width / 2 + transform.position.x - size / 2;
- const y = renderer.height / 2 + transform.position.y - size / 2;
-
- renderer.context.save();
- renderer.context.globalAlpha = opacity;
-
- if (transform.rotate !== 0) {
- const centerX = x + size / 2;
- const centerY = y + size / 2;
- renderer.context.translate(centerX, centerY);
- renderer.context.rotate((transform.rotate * Math.PI) / 180);
- renderer.context.translate(-centerX, -centerY);
- }
-
- renderer.context.drawImage(this.image, x, y, size, size);
- renderer.context.restore();
+ this.renderVisual({
+ renderer,
+ source: this.image,
+ sourceWidth: 200,
+ sourceHeight: 200,
+ });
}
}
diff --git a/apps/web/src/services/renderer/nodes/video-node.ts b/apps/web/src/services/renderer/nodes/video-node.ts
index 841eb459..8e2ddccd 100644
--- a/apps/web/src/services/renderer/nodes/video-node.ts
+++ b/apps/web/src/services/renderer/nodes/video-node.ts
@@ -1,37 +1,14 @@
import type { CanvasRenderer } from "../canvas-renderer";
-import { BaseNode } from "./base-node";
+import { VisualNode, type VisualNodeParams } from "./visual-node";
import { videoCache } from "@/services/video-cache/service";
-const VIDEO_EPSILON = 1 / 1000;
-
-export interface VideoNodeParams {
+export interface VideoNodeParams extends VisualNodeParams {
url: string;
file: File;
mediaId: string;
- duration: number;
- timeOffset: number;
- trimStart: number;
- trimEnd: number;
- x?: number;
- y?: number;
- width?: number;
- height?: number;
- opacity?: number;
}
-export class VideoNode extends BaseNode<VideoNodeParams> {
- private getVideoTime(time: number) {
- return time - this.params.timeOffset + this.params.trimStart;
- }
-
- private isInRange(time: number) {
- const videoTime = this.getVideoTime(time);
- return (
- videoTime >= this.params.trimStart - VIDEO_EPSILON &&
- videoTime < this.params.trimStart + this.params.duration
- );
- }
-
+export class VideoNode extends VisualNode<VideoNodeParams> {
async render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {
await super.render({ renderer, time });
@@ -39,7 +16,7 @@ export class VideoNode extends BaseNode<VideoNodeParams> {
return;
}
- const videoTime = this.getVideoTime(time);
+ const videoTime = this.getLocalTime(time);
const frame = await videoCache.getFrameAt({
mediaId: this.params.mediaId,
file: this.params.file,
@@ -47,36 +24,12 @@ export class VideoNode extends BaseNode<VideoNodeParams> {
});
if (frame) {
- renderer.context.save();
-
- if (this.params.opacity !== undefined) {
- renderer.context.globalAlpha = this.params.opacity;
- }
-
- if (
- this.params.x !== undefined &&
- this.params.y !== undefined &&
- this.params.width !== undefined &&
- this.params.height !== undefined
- ) {
- renderer.context.drawImage(
- frame.canvas,
- this.params.x,
- this.params.y,
- this.params.width,
- this.params.height,
- );
- } else {
- renderer.context.drawImage(
- frame.canvas,
- 0,
- 0,
- renderer.width,
- renderer.height,
- );
- }
-
- renderer.context.restore();
+ this.renderVisual({
+ renderer,
+ source: frame.canvas,
+ sourceWidth: frame.canvas.width,
+ sourceHeight: frame.canvas.height,
+ });
}
}
}
diff --git a/apps/web/src/services/renderer/nodes/visual-node.ts b/apps/web/src/services/renderer/nodes/visual-node.ts
new file mode 100644
index 00000000..9c6c88ca
--- /dev/null
+++ b/apps/web/src/services/renderer/nodes/visual-node.ts
@@ -0,0 +1,67 @@
+import type { CanvasRenderer } from "../canvas-renderer";
+import { BaseNode } from "./base-node";
+import type { Transform } from "@/types/timeline";
+
+const VISUAL_EPSILON = 1 / 1000;
+
+export interface VisualNodeParams {
+ duration: number;
+ timeOffset: number;
+ trimStart: number;
+ trimEnd: number;
+ transform: Transform;
+ opacity: number;
+}
+
+export abstract class VisualNode<
+ Params extends VisualNodeParams = VisualNodeParams,
+> extends BaseNode<Params> {
+ protected getLocalTime(time: number): number {
+ return time - this.params.timeOffset + this.params.trimStart;
+ }
+
+ protected isInRange(time: number): boolean {
+ const localTime = this.getLocalTime(time);
+ return (
+ localTime >= this.params.trimStart - VISUAL_EPSILON &&
+ localTime < this.params.trimStart + this.params.duration
+ );
+ }
+
+ protected renderVisual({
+ renderer,
+ source,
+ sourceWidth,
+ sourceHeight,
+ }: {
+ renderer: CanvasRenderer;
+ source: CanvasImageSource;
+ sourceWidth: number;
+ sourceHeight: number;
+ }): void {
+ renderer.context.save();
+
+ const { transform, opacity } = this.params;
+ const containScale = Math.min(
+ renderer.width / sourceWidth,
+ renderer.height / sourceHeight,
+ );
+ const scaledWidth = sourceWidth * containScale * transform.scale;
+ const scaledHeight = sourceHeight * containScale * transform.scale;
+ const x = renderer.width / 2 + transform.position.x - scaledWidth / 2;
+ const y = renderer.height / 2 + transform.position.y - scaledHeight / 2;
+
+ renderer.context.globalAlpha = opacity;
+
+ if (transform.rotate !== 0) {
+ const centerX = x + scaledWidth / 2;
+ const centerY = y + scaledHeight / 2;
+ renderer.context.translate(centerX, centerY);
+ renderer.context.rotate((transform.rotate * Math.PI) / 180);
+ renderer.context.translate(-centerX, -centerY);
+ }
+
+ renderer.context.drawImage(source, x, y, scaledWidth, scaledHeight);
+ renderer.context.restore();
+ }
+}
diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts
index 5d33524e..d1b8217e 100644
--- a/apps/web/src/services/renderer/scene-builder.ts
+++ b/apps/web/src/services/renderer/scene-builder.ts
@@ -64,6 +64,8 @@ export function buildScene(params: BuildSceneParams) {
timeOffset: element.startTime,
trimStart: element.trimStart,
trimEnd: element.trimEnd,
+ transform: element.transform,
+ opacity: element.opacity,
}),
);
}
@@ -75,6 +77,8 @@ export function buildScene(params: BuildSceneParams) {
timeOffset: element.startTime,
trimStart: element.trimStart,
trimEnd: element.trimEnd,
+ transform: element.transform,
+ opacity: element.opacity,
}),
);
}