UNPKG

id-scanner-lib

Version:

Browser-based ID card, QR code, and face recognition scanner with liveness detection

171 lines (156 loc) 4.28 kB
/** * @file Canvas 对象池 * @description 提供 OffscreenCanvas/HTMLCanvasElement 的复用机制,减少内存分配和 GC 压力 * @module core/utils/canvas-pool */ /** * Canvas 池条目 */ export interface PooledCanvas { canvas: OffscreenCanvas | HTMLCanvasElement; width: number; height: number; inUse: boolean; } interface PoolEntry { canvas: OffscreenCanvas | HTMLCanvasElement; width: number; height: number; inUse: boolean; lastUsed: number; } /** * Canvas 对象池 * * 复用 Canvas 元素,避免频繁创建和销毁导致的内存抖动 * * @example * ```typescript * const pool = new CanvasPool(); * const acquired = pool.acquire(640, 480); * // 使用 canvas 进行绘制... * pool.release(acquired); * ``` */ export class CanvasPool { private _pool: PoolEntry[] = []; private _maxSize: number = 4; private _useOffscreen: boolean; /** * 创建 Canvas 池 * @param maxSize 最大池大小 * @param useOffscreen 是否使用 OffscreenCanvas */ constructor(maxSize: number = 4, useOffscreen: boolean = true) { this._maxSize = maxSize; this._useOffscreen = useOffscreen && typeof OffscreenCanvas !== 'undefined'; } /** * 从池中获取 Canvas * * 1. 查找尺寸匹配的可用 canvas(允许 10% 误差) * 2. 无匹配则创建新的 * 3. 超出 _maxSize 则回收最旧的 inUse=false 的 * 4. 返回 PooledCanvas,标记 inUse=true * * @param width 宽度 * @param height 高度 */ acquire(width: number, height: number): PooledCanvas { // 1. 查找尺寸匹配的可用 canvas(允许 10% 误差) const matched = this._pool.find(p => !p.inUse && Math.abs(p.width - width) / width <= 0.1 && Math.abs(p.height - height) / height <= 0.1 ); if (matched) { matched.inUse = true; matched.lastUsed = Date.now(); return { canvas: matched.canvas, width: matched.width, height: matched.height, inUse: true, }; } // 2. 无匹配则创建新的 let canvas: OffscreenCanvas | HTMLCanvasElement; if (this._useOffscreen) { canvas = new OffscreenCanvas(width, height); } else { // 在非 OffscreenCanvas 环境(如 Node.js 测试)回退到 HTMLCanvasElement if (typeof document !== 'undefined') { canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; } else { // 无法创建 canvas throw new Error('CanvasPool: 无法创建 canvas (OffscreenCanvas 不可用,且 document 不存在)'); } } const entry: PoolEntry = { canvas, width, height, inUse: true, lastUsed: Date.now(), }; // 3. 超出 _maxSize 则回收最旧的 inUse=false 的 if (this._pool.length >= this._maxSize) { const oldestIdx = this._findOldestAvailableIndex(); if (oldestIdx !== -1) { this._pool.splice(oldestIdx, 1); } } this._pool.push(entry); return { canvas: entry.canvas, width: entry.width, height: entry.height, inUse: true, }; } /** * 归还 Canvas 到池中 * @param pooled 要归还的 PooledCanvas */ release(pooled: PooledCanvas): void { const entry = this._pool.find(p => p.canvas === pooled.canvas); if (entry) { entry.inUse = false; entry.lastUsed = Date.now(); } } /** * 清空池 */ clear(): void { this._pool = []; } /** * 获取池统计信息 */ getStats(): { total: number; inUse: number; available: number } { return { total: this._pool.length, inUse: this._pool.filter(p => p.inUse).length, available: this._pool.filter(p => !p.inUse).length, }; } /** * 查找最旧的可用条目索引 */ private _findOldestAvailableIndex(): number { let oldestIdx = -1; let oldestTime = Infinity; for (let i = 0; i < this._pool.length; i++) { if (!this._pool[i].inUse && this._pool[i].lastUsed < oldestTime) { oldestTime = this._pool[i].lastUsed; oldestIdx = i; } } return oldestIdx; } } /** 全局共享的 Canvas 池实例 */ export const globalCanvasPool = new CanvasPool();