UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

260 lines (259 loc) 9.52 kB
import { _defineProperty } from "../../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs"; import { config } from "../config.mjs"; import { createCanvasElementFor } from "../util/misc/dom.mjs"; //#region src/filters/WebGLFilterBackend.ts var WebGLFilterBackend = class { constructor({ tileSize = config.textureSize } = {}) { _defineProperty( this, /** * Define ... **/ "aPosition", new Float32Array([ 0, 0, 0, 1, 1, 0, 1, 1 ]) ); _defineProperty( this, /** * Experimental. This object is a sort of repository of help layers used to avoid * of recreating them during frequent filtering. If you are previewing a filter with * a slider you probably do not want to create help layers every filter step. * in this object there will be appended some canvases, created once, resized sometimes * cleared never. Clearing is left to the developer. **/ "resources", {} ); this.tileSize = tileSize; this.setupGLContext(tileSize, tileSize); this.captureGPUInfo(); } /** * Setup a WebGL context suitable for filtering, and bind any needed event handlers. */ setupGLContext(width, height) { this.dispose(); this.createWebGLCanvas(width, height); } /** * Create a canvas element and associated WebGL context and attaches them as * class properties to the GLFilterBackend class. */ createWebGLCanvas(width, height) { const canvas = createCanvasElementFor({ width, height }); const gl = canvas.getContext("webgl", { alpha: true, premultipliedAlpha: false, depth: false, stencil: false, antialias: false }); if (!gl) return; gl.clearColor(0, 0, 0, 0); this.canvas = canvas; this.gl = gl; } /** * Attempts to apply the requested filters to the source provided, drawing the filtered output * to the provided target canvas. * * @param {Array} filters The filters to apply. * @param {TexImageSource} source The source to be filtered. * @param {Number} width The width of the source input. * @param {Number} height The height of the source input. * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. * @param {String|undefined} cacheKey A key used to cache resources related to the source. If * omitted, caching will be skipped. */ applyFilters(filters, source, width, height, targetCanvas, cacheKey) { const gl = this.gl; const ctx = targetCanvas.getContext("2d"); if (!gl || !ctx) return; let cachedTexture; if (cacheKey) cachedTexture = this.getCachedTexture(cacheKey, source); const pipelineState = { originalWidth: source.width || source.naturalWidth || 0, originalHeight: source.height || source.naturalHeight || 0, sourceWidth: width, sourceHeight: height, destinationWidth: width, destinationHeight: height, context: gl, sourceTexture: this.createTexture(gl, width, height, !cachedTexture ? source : void 0), targetTexture: this.createTexture(gl, width, height), originalTexture: cachedTexture || this.createTexture(gl, width, height, !cachedTexture ? source : void 0), passes: filters.length, webgl: true, aPosition: this.aPosition, programCache: this.programCache, pass: 0, filterBackend: this, targetCanvas }; const tempFbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo); filters.forEach((filter) => { filter && filter.applyTo(pipelineState); }); resizeCanvasIfNeeded(pipelineState); this.copyGLTo2D(gl, pipelineState); gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(pipelineState.sourceTexture); gl.deleteTexture(pipelineState.targetTexture); gl.deleteFramebuffer(tempFbo); ctx.setTransform(1, 0, 0, 1, 0, 0); return pipelineState; } /** * Detach event listeners, remove references, and clean up caches. */ dispose() { if (this.canvas) { this.canvas = null; this.gl = null; } this.clearWebGLCaches(); } /** * Wipe out WebGL-related caches. */ clearWebGLCaches() { this.programCache = {}; this.textureCache = {}; } /** * Create a WebGL texture object. * * Accepts specific dimensions to initialize the texture to or a source image. * * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. * @param {number} width The width to initialize the texture at. * @param {number} height The height to initialize the texture. * @param {TexImageSource} textureImageSource A source for the texture data. * @param {number} filter gl.NEAREST default or gl.LINEAR filters for the texture. * This filter is very useful for LUTs filters. If you need interpolation use gl.LINEAR * @returns {WebGLTexture} */ createTexture(gl, width, height, textureImageSource, filter) { const { NEAREST, TEXTURE_2D, RGBA, UNSIGNED_BYTE, CLAMP_TO_EDGE, TEXTURE_MAG_FILTER, TEXTURE_MIN_FILTER, TEXTURE_WRAP_S, TEXTURE_WRAP_T } = gl; const texture = gl.createTexture(); gl.bindTexture(TEXTURE_2D, texture); gl.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, filter || NEAREST); gl.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, filter || NEAREST); gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE); gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE); if (textureImageSource) gl.texImage2D(TEXTURE_2D, 0, RGBA, RGBA, UNSIGNED_BYTE, textureImageSource); else gl.texImage2D(TEXTURE_2D, 0, RGBA, width, height, 0, RGBA, UNSIGNED_BYTE, null); return texture; } /** * Can be optionally used to get a texture from the cache array * * If an existing texture is not found, a new texture is created and cached. * * @param {String} uniqueId A cache key to use to find an existing texture. * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the * texture cache entry if one does not already exist. */ getCachedTexture(uniqueId, textureImageSource, filter) { const { textureCache } = this; if (textureCache[uniqueId]) return textureCache[uniqueId]; else { const texture = this.createTexture(this.gl, textureImageSource.width, textureImageSource.height, textureImageSource, filter); if (texture) textureCache[uniqueId] = texture; return texture; } } /** * Clear out cached resources related to a source image that has been * filtered previously. * * @param {String} cacheKey The cache key provided when the source image was filtered. */ evictCachesForKey(cacheKey) { if (this.textureCache[cacheKey]) { this.gl.deleteTexture(this.textureCache[cacheKey]); delete this.textureCache[cacheKey]; } } /** * Copy an input WebGL canvas on to an output 2D canvas. * * The WebGL canvas is assumed to be upside down, with the top-left pixel of the * desired output image appearing in the bottom-left corner of the WebGL canvas. * * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. * @param {Object} pipelineState The 2D target canvas to copy on to. */ copyGLTo2D(gl, pipelineState) { const glCanvas = gl.canvas, targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext("2d"); if (!ctx) return; ctx.translate(0, targetCanvas.height); ctx.scale(1, -1); const sourceY = glCanvas.height - targetCanvas.height; ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0, targetCanvas.width, targetCanvas.height); } /** * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra). * * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. * @param {Object} pipelineState The 2D target canvas to copy on to. */ copyGLTo2DPutImageData(gl, pipelineState) { const ctx = pipelineState.targetCanvas.getContext("2d"), dWidth = pipelineState.destinationWidth, dHeight = pipelineState.destinationHeight, numBytes = dWidth * dHeight * 4; if (!ctx) return; const u8 = new Uint8Array(this.imageBuffer, 0, numBytes); const u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes); gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8); const imgData = new ImageData(u8Clamped, dWidth, dHeight); ctx.putImageData(imgData, 0, 0); } /** * Attempt to extract GPU information strings from a WebGL context. * * Useful information when debugging or blacklisting specific GPUs. * * @returns {Object} A GPU info object with renderer and vendor strings. */ captureGPUInfo() { if (this.gpuInfo) return this.gpuInfo; const gl = this.gl, gpuInfo = { renderer: "", vendor: "" }; if (!gl) return gpuInfo; const ext = gl.getExtension("WEBGL_debug_renderer_info"); if (ext) { const renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); const vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); if (renderer) gpuInfo.renderer = renderer.toLowerCase(); if (vendor) gpuInfo.vendor = vendor.toLowerCase(); } this.gpuInfo = gpuInfo; return gpuInfo; } }; function resizeCanvasIfNeeded(pipelineState) { const targetCanvas = pipelineState.targetCanvas, width = targetCanvas.width, height = targetCanvas.height, dWidth = pipelineState.destinationWidth, dHeight = pipelineState.destinationHeight; if (width !== dWidth || height !== dHeight) { targetCanvas.width = dWidth; targetCanvas.height = dHeight; } } //#endregion export { WebGLFilterBackend }; //# sourceMappingURL=WebGLFilterBackend.mjs.map