124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import { ICompositeOptions, ICompositor } from './ICompositor';
|
|
|
|
/**
|
|
* Canvas 2D three-pass compositor.
|
|
*
|
|
* Extracted from V1's {@code runPostProcessing} and V2's {@code _compositeFallback}.
|
|
* Uses {@code globalCompositeOperation} to blend the segmentation mask, camera
|
|
* foreground, and background into a single output frame on the CPU.
|
|
*
|
|
* Falls back gracefully when context creation fails. Callers should check
|
|
* {@link Canvas2DCompositor#isAvailable} before use.
|
|
*/
|
|
export default class Canvas2DCompositor implements ICompositor {
|
|
_canvas: HTMLCanvasElement;
|
|
_ctx: CanvasRenderingContext2D | null = null;
|
|
_maskCanvas: HTMLCanvasElement | null = null;
|
|
_maskCanvasCtx: CanvasRenderingContext2D | null = null;
|
|
|
|
/**
|
|
* Creates a Canvas2DCompositor with an internal output canvas and mask helper canvas.
|
|
*/
|
|
constructor() {
|
|
this._canvas = document.createElement('canvas');
|
|
this._ctx = this._canvas.getContext('2d');
|
|
this._maskCanvas = document.createElement('canvas');
|
|
this._maskCanvasCtx = this._maskCanvas.getContext('2d');
|
|
}
|
|
|
|
/**
|
|
* Returns true if a 2D rendering context is active and ready.
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
get isAvailable(): boolean {
|
|
return this._ctx !== null;
|
|
}
|
|
|
|
/**
|
|
* Returns the canvas element that receives the composited output each frame.
|
|
*
|
|
* @returns {HTMLCanvasElement}
|
|
*/
|
|
get outputCanvas(): HTMLCanvasElement {
|
|
return this._canvas;
|
|
}
|
|
|
|
/**
|
|
* Composites one frame using three-pass Canvas 2D blending.
|
|
*
|
|
* 1. Draw the blurred segmentation mask (person silhouette).
|
|
* 2. Clip the camera foreground to the mask via {@code source-in}.
|
|
* 3. Draw the background behind the person via {@code destination-over}.
|
|
*
|
|
* The mask {@code ImageData} is painted onto a persistent helper canvas via
|
|
* {@code putImageData} which writes raw bytes without premultiplied-alpha
|
|
* conversion, then scaled to the output dimensions with {@code drawImage}.
|
|
*
|
|
* @param {CanvasImageSource} camera - Live camera frame.
|
|
* @param {CanvasImageSource} background - Pre-rendered background (blur or image).
|
|
* @param {ImageData} maskData - Raw segmentation mask bytes.
|
|
* @param {ICompositeOptions} options - Edge thresholds, blur radius, and first-frame flag.
|
|
* @returns {void}
|
|
*/
|
|
composite(
|
|
camera: CanvasImageSource,
|
|
background: CanvasImageSource,
|
|
maskData: ImageData,
|
|
options: ICompositeOptions): void {
|
|
const ctx = this._ctx;
|
|
const maskCanvas = this._maskCanvas;
|
|
|
|
if (!ctx || !maskCanvas) {
|
|
return;
|
|
}
|
|
const { width, height } = this._canvas;
|
|
|
|
ctx.globalCompositeOperation = 'copy';
|
|
|
|
if (maskCanvas.width !== maskData.width
|
|
|| maskCanvas.height !== maskData.height) {
|
|
maskCanvas.width = maskData.width;
|
|
maskCanvas.height = maskData.height;
|
|
}
|
|
|
|
// putImageData writes raw bytes — no premultiplied-alpha distortion.
|
|
this._maskCanvasCtx?.putImageData(maskData, 0, 0);
|
|
|
|
ctx.filter = options.cssBlurFilter;
|
|
ctx.drawImage(maskCanvas, 0, 0, width, height);
|
|
|
|
// Clip foreground to mask.
|
|
ctx.globalCompositeOperation = 'source-in';
|
|
ctx.filter = 'none';
|
|
ctx.drawImage(camera, 0, 0, width, height);
|
|
|
|
// Draw background behind the person.
|
|
ctx.globalCompositeOperation = 'destination-over';
|
|
ctx.drawImage(background as CanvasImageSource, 0, 0, width, height);
|
|
}
|
|
|
|
/**
|
|
* Releases all rendering contexts and helper canvases.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
dispose(): void {
|
|
this._ctx = null;
|
|
this._maskCanvas = null;
|
|
this._maskCanvasCtx = null;
|
|
}
|
|
|
|
/**
|
|
* Resizes the output canvas to the given dimensions.
|
|
*
|
|
* @param {number} width - New width in pixels.
|
|
* @param {number} height - New height in pixels.
|
|
* @returns {void}
|
|
*/
|
|
resize(width: number, height: number): void {
|
|
this._canvas.width = width;
|
|
this._canvas.height = height;
|
|
}
|
|
}
|