summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/nodes/visual-node.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-02-08 22:21:17 +0100
committerMaze Winther <mazewinther@gmail.com>2026-02-08 22:21:17 +0100
commite50a817b97b87baa0cd8fdde0fdb5d607143c1c3 (patch)
tree8f33d19153be9a6d9fe81b1a7e7a54179ab88feb /apps/web/src/services/renderer/nodes/visual-node.ts
parent484ce47867b1c92c83e4cfab9fa8371bb6e68e12 (diff)
downloadopencut-e50a817b97b87baa0cd8fdde0fdb5d607143c1c3.tar.gz
add transform + few fixes
feat: transform feat: batch command fix: slider undo fix: timeline zoom perf refactor: make updateElement support multiple
Diffstat (limited to 'apps/web/src/services/renderer/nodes/visual-node.ts')
-rw-r--r--apps/web/src/services/renderer/nodes/visual-node.ts67
1 files changed, 67 insertions, 0 deletions
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();
+ }
+}