diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-02-08 22:21:17 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-02-08 22:21:17 +0100 |
| commit | e50a817b97b87baa0cd8fdde0fdb5d607143c1c3 (patch) | |
| tree | 8f33d19153be9a6d9fe81b1a7e7a54179ab88feb /apps/web/src/services/renderer/nodes/visual-node.ts | |
| parent | 484ce47867b1c92c83e4cfab9fa8371bb6e68e12 (diff) | |
| download | opencut-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.ts | 67 |
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(); + } +} |
