summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/nodes/blur-background-node.ts
blob: 504161d9cd84083c72675077463cde09d29340b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import type { CanvasRenderer } from "../canvas-renderer";
import { BaseNode } from "./base-node";

export type BlurBackgroundNodeParams = {
	blurIntensity: number;
	contentNodes: BaseNode[];
};

export class BlurBackgroundNode extends BaseNode<BlurBackgroundNodeParams> {
	private blurIntensity: number;
	private contentNodes: BaseNode[];

	constructor(params: BlurBackgroundNodeParams) {
		super(params);
		this.blurIntensity = params.blurIntensity;
		this.contentNodes = params.contentNodes;
	}

	async render({
		renderer,
		time,
	}: {
		renderer: CanvasRenderer;
		time: number;
	}): Promise<void> {
		let offscreen: OffscreenCanvas | HTMLCanvasElement;
		let offscreenCtx:
			| OffscreenCanvasRenderingContext2D
			| CanvasRenderingContext2D;

		try {
			offscreen = new OffscreenCanvas(renderer.width, renderer.height);
			const ctx = offscreen.getContext("2d");
			if (!ctx) {
				throw new Error("failed to get offscreen canvas context");
			}
			offscreenCtx = ctx;
		} catch {
			offscreen = document.createElement("canvas");
			offscreen.width = renderer.width;
			offscreen.height = renderer.height;
			const ctx = offscreen.getContext("2d");
			if (!ctx) {
				throw new Error("failed to get canvas context");
			}
			offscreenCtx = ctx;
		}

		const originalContext = renderer.context;
		renderer.context = offscreenCtx;

		for (const node of this.contentNodes) {
			await node.render({ renderer, time });
		}

		renderer.context = originalContext;

		const zoomScale = 1.4;
		const scaledWidth = renderer.width * zoomScale;
		const scaledHeight = renderer.height * zoomScale;
		const offsetX = (renderer.width - scaledWidth) / 2;
		const offsetY = (renderer.height - scaledHeight) / 2;

		renderer.context.save();
		renderer.context.filter = `blur(${this.blurIntensity}px)`;
		renderer.context.drawImage(
			offscreen as CanvasImageSource,
			offsetX,
			offsetY,
			scaledWidth,
			scaledHeight,
		);
		renderer.context.restore();
	}
}