diff options
| author | Maze <167211895+mazeincoding@users.noreply.github.com> | 2026-01-31 00:20:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 00:20:04 +0100 |
| commit | 7bf0984698e43b9aa2dd879f65951318ed410390 (patch) | |
| tree | ccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/services/renderer/nodes/sticker-node.ts | |
| parent | 0173db99448d1c4c6415dcf15272e17551b48b04 (diff) | |
| download | opencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz | |
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/services/renderer/nodes/sticker-node.ts')
| -rw-r--r-- | apps/web/src/services/renderer/nodes/sticker-node.ts | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/apps/web/src/services/renderer/nodes/sticker-node.ts b/apps/web/src/services/renderer/nodes/sticker-node.ts new file mode 100644 index 00000000..5af13a6f --- /dev/null +++ b/apps/web/src/services/renderer/nodes/sticker-node.ts @@ -0,0 +1,87 @@ +import type { CanvasRenderer } from "../canvas-renderer"; +import { BaseNode, type BaseNodeParams } from "./base-node"; +import type { Transform } from "@/types/timeline"; + +const STICKER_EPSILON = 1 / 1000; + +export type StickerNodeParams = BaseNodeParams & { + iconName: string; + duration: number; + timeOffset: number; + trimStart: number; + trimEnd: number; + transform: Transform; + opacity: number; + color?: string; +}; + +export class StickerNode extends BaseNode<StickerNodeParams> { + private image?: HTMLImageElement; + private readyPromise: Promise<void>; + + constructor(params: StickerNodeParams) { + super(params); + this.readyPromise = this.load(); + } + + private async load() { + const image = new Image(); + this.image = image; + const color = this.params.color + ? `&color=${encodeURIComponent(this.params.color)}` + : ""; + const url = `https://api.iconify.design/${this.params.iconName}.svg?width=200&height=200${color}`; + + await new Promise<void>((resolve, reject) => { + image.onload = () => resolve(); + image.onerror = () => + reject(new Error(`Failed to load sticker: ${this.params.iconName}`)); + image.src = url; + }); + } + + 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 }); + + if (!this.isInRange(time)) { + return; + } + + await this.readyPromise; + + if (!this.image) { + 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(); + } +} |
