From 0f92e3023e4872cf217f4b1cd857e1ae427877fd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Oct 2021 11:39:18 -0400 Subject: [PATCH] optimize image preprocessing --- demo/index.js | 15 +-- src/image/image.ts | 31 ++--- src/image/imagefx.ts | 293 +++++++++++++++++++++++-------------------- 3 files changed, 175 insertions(+), 164 deletions(-) diff --git a/demo/index.js b/demo/index.js index 194846cd..ecb24dbd 100644 --- a/demo/index.js +++ b/demo/index.js @@ -31,9 +31,9 @@ import jsonView from './helpers/jsonview.js'; let human; let userConfig = { - face: { enabled: false }, - body: { enabled: false }, - hand: { enabled: false }, + // face: { enabled: false }, + // body: { enabled: false }, + // hand: { enabled: false }, /* warmup: 'none', backend: 'humangl', @@ -41,10 +41,7 @@ let userConfig = { wasmPath: 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@3.9.0/dist/', async: false, cacheSensitivity: 0.75, - filter: { - enabled: false, - flip: false, - }, + filter: { enabled: false, flip: false }, face: { enabled: false, detector: { return: false, rotation: true }, mesh: { enabled: false }, @@ -54,11 +51,9 @@ let userConfig = { }, object: { enabled: false }, gesture: { enabled: true }, - // hand: { enabled: false }, hand: { enabled: true, maxDetected: 1, minConfidence: 0.5, detector: { modelPath: 'handtrack.json' } }, body: { enabled: false }, // body: { enabled: true, modelPath: 'movenet-multipose.json' }, - // body: { enabled: true, modelPath: 'posenet.json' }, segmentation: { enabled: false }, */ }; @@ -94,7 +89,7 @@ const ui = { autoPlay: false, // start webcam & detection on load // internal variables - exceptionHandler: false, // should capture all unhandled exceptions + exceptionHandler: true, // should capture all unhandled exceptions busy: false, // internal camera busy flag menuWidth: 0, // internal menuHeight: 0, // internal diff --git a/src/image/image.ts b/src/image/image.ts index 49f058c7..9ce2325d 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -134,21 +134,22 @@ export function process(input: Input, config: Config, getTensor: boolean = true) env.filter = !!fx; if (!fx) return { tensor: null, canvas: inCanvas }; fx.reset(); - fx.addFilter('brightness', config.filter.brightness); // must have at least one filter enabled - if (config.filter.contrast !== 0) fx.addFilter('contrast', config.filter.contrast); - if (config.filter.sharpness !== 0) fx.addFilter('sharpen', config.filter.sharpness); - if (config.filter.blur !== 0) fx.addFilter('blur', config.filter.blur); - if (config.filter.saturation !== 0) fx.addFilter('saturation', config.filter.saturation); - if (config.filter.hue !== 0) fx.addFilter('hue', config.filter.hue); - if (config.filter.negative) fx.addFilter('negative'); - if (config.filter.sepia) fx.addFilter('sepia'); - if (config.filter.vintage) fx.addFilter('brownie'); - if (config.filter.sepia) fx.addFilter('sepia'); - if (config.filter.kodachrome) fx.addFilter('kodachrome'); - if (config.filter.technicolor) fx.addFilter('technicolor'); - if (config.filter.polaroid) fx.addFilter('polaroid'); - if (config.filter.pixelate !== 0) fx.addFilter('pixelate', config.filter.pixelate); - outCanvas = fx.apply(inCanvas); + if (config.filter.brightness !== 0) fx.add('brightness', config.filter.brightness); + if (config.filter.contrast !== 0) fx.add('contrast', config.filter.contrast); + if (config.filter.sharpness !== 0) fx.add('sharpen', config.filter.sharpness); + if (config.filter.blur !== 0) fx.add('blur', config.filter.blur); + if (config.filter.saturation !== 0) fx.add('saturation', config.filter.saturation); + if (config.filter.hue !== 0) fx.add('hue', config.filter.hue); + if (config.filter.negative) fx.add('negative'); + if (config.filter.sepia) fx.add('sepia'); + if (config.filter.vintage) fx.add('brownie'); + if (config.filter.sepia) fx.add('sepia'); + if (config.filter.kodachrome) fx.add('kodachrome'); + if (config.filter.technicolor) fx.add('technicolor'); + if (config.filter.polaroid) fx.add('polaroid'); + if (config.filter.pixelate !== 0) fx.add('pixelate', config.filter.pixelate); + if (fx.get() > 0) outCanvas = fx.apply(inCanvas); + else outCanvas = fx.draw(inCanvas); } else { copy(inCanvas, outCanvas); // if no filters applied, output canvas is input canvas if (fx) fx = null; diff --git a/src/image/imagefx.ts b/src/image/imagefx.ts index 4374328e..6ae23adf 100644 --- a/src/image/imagefx.ts +++ b/src/image/imagefx.ts @@ -1,7 +1,6 @@ /** * Image Filters in WebGL algoritm implementation * Based on: [WebGLImageFilter](https://github.com/phoboslab/WebGLImageFilter) - * This module is written in ES5 JS and does not conform to code and style standards */ import * as shaders from './imagefxshaders'; @@ -13,11 +12,11 @@ class GLProgram { id: WebGLProgram; constructor(gl, vertexSource, fragmentSource) { this.gl = gl; - const _vsh = this.compile(vertexSource, this.gl.VERTEX_SHADER); - const _fsh = this.compile(fragmentSource, this.gl.FRAGMENT_SHADER); + const vertexShader = this.compile(vertexSource, this.gl.VERTEX_SHADER); + const fragmentShader = this.compile(fragmentSource, this.gl.FRAGMENT_SHADER); this.id = this.gl.createProgram() as WebGLProgram; - this.gl.attachShader(this.id, _vsh); - this.gl.attachShader(this.id, _fsh); + this.gl.attachShader(this.id, vertexShader); + this.gl.attachShader(this.id, fragmentShader); this.gl.linkProgram(this.id); if (!this.gl.getProgramParameter(this.id, this.gl.LINK_STATUS)) throw new Error(`filter: gl link failed: ${this.gl.getProgramInfoLog(this.id)}`); this.gl.useProgram(this.id); @@ -45,53 +44,46 @@ class GLProgram { }; } -export function GLImageFilter(params) { - if (!params) params = { }; - let _drawCount = 0; - let _sourceTexture = null; - let _lastInChain = false; - let _currentFramebufferIndex = -1; - let _tempFramebuffers: [null, null] | [{ fbo: any, texture: any }] = [null, null]; - let _filterChain: Record[] = []; - let _width = -1; - let _height = -1; - let _vertexBuffer = null; - let _currentProgram: GLProgram | null = null; - const _canvas = params.canvas || typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(100, 100) : document.createElement('canvas'); - const _shaderProgramCache = { }; // key is the shader program source, value is the compiled program +// function that is instantiated as class so it has private this members +/** + * @class GLImageFilter + * @property {function} reset reset current filter chain + * @property {function} add add specified filter to filter chain + * @property {function} apply execute filter chain and draw result + * @property {function} draw just draw input to result + * @param {HTMLCanvasElement | OffscreenCanvas} canvas use specific canvas for all webgl bindings + */ +export function GLImageFilter(params = {}) { + let drawCount = 0; + let sourceTexture: WebGLTexture | null = null; + let lastInChain = false; + let currentFramebufferIndex = -1; + let tempFramebuffers: [null, null] | [{ fbo: WebGLFramebuffer | null, texture: WebGLTexture | null }] = [null, null]; + let filterChain: Record[] = []; + let vertexBuffer: WebGLBuffer | null = null; + let currentProgram: GLProgram | null = null; + const canvas = params['canvas'] || typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(100, 100) : document.createElement('canvas'); + const shaderProgramCache = { }; // key is the shader program source, value is the compiled program const DRAW = { INTERMEDIATE: 1 }; - const gl = _canvas.getContext('webgl'); + const gl = canvas.getContext('webgl') as WebGLRenderingContext; if (!gl) throw new Error('filter: cannot get webgl context'); - this.addFilter = function (name) { - // eslint-disable-next-line prefer-rest-params - const args = Array.prototype.slice.call(arguments, 1); - const filter = _filter[name]; - _filterChain.push({ func: filter, args }); - }; - - this.reset = function () { - _filterChain = []; - }; - - const _resize = function (width, height) { - if (width === _width && height === _height) return; // Same width/height? Nothing to do here - _canvas.width = width; - _width = width; - _canvas.height = height; - _height = height; - if (!_vertexBuffer) { // Create the context if we don't have it yet + function resize(width, height) { + if (width === canvas.width && height === canvas.height) return; // Same width/height? Nothing to do here + canvas.width = width; + canvas.height = height; + if (!vertexBuffer) { // Create the context if we don't have it yet const vertices = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, -1, 1, 0, 0, 1, -1, 1, 1, 1, 1, 1, 0]); // Create the vertex buffer for the two triangles [x, y, u, v] * 6 - // eslint-disable-next-line no-unused-expressions - (_vertexBuffer = gl.createBuffer(), gl.bindBuffer(gl.ARRAY_BUFFER, _vertexBuffer)); + vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); } - gl.viewport(0, 0, _width, _height); - _tempFramebuffers = [null, null]; // Delete old temp framebuffers - }; + gl.viewport(0, 0, canvas.width, canvas.height); + tempFramebuffers = [null, null]; // Delete old temp framebuffers + } - const _createFramebufferTexture = function (width, height) { + function createFramebufferTexture(width, height) { const fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); const renderbuffer = gl.createRenderbuffer(); @@ -107,77 +99,54 @@ export function GLImageFilter(params) { gl.bindTexture(gl.TEXTURE_2D, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); return { fbo, texture }; - }; + } - const _getTempFramebuffer = function (index) { - _tempFramebuffers[index] = _tempFramebuffers[index] || _createFramebufferTexture(_width, _height); - return _tempFramebuffers[index]; - }; + function getTempFramebuffer(index) { + tempFramebuffers[index] = tempFramebuffers[index] || createFramebufferTexture(canvas.width, canvas.height); + return tempFramebuffers[index]; + } - const _draw = function (flags = 0) { - if (!_currentProgram) return; - let source = null; - let target = null; + function draw(flags = 0) { + if (!currentProgram) return; + let source: WebGLTexture | null = null; + let target: WebGLFramebuffer | null = null; let flipY = false; - if (_drawCount === 0) source = _sourceTexture; // First draw call - use the source texture - else source = _getTempFramebuffer(_currentFramebufferIndex)?.texture; // All following draw calls use the temp buffer last drawn to - _drawCount++; - if (_lastInChain && !(flags & DRAW.INTERMEDIATE)) { // Last filter in our chain - draw directly to the WebGL Canvas. We may also have to flip the image vertically now + if (drawCount === 0) source = sourceTexture; // First draw call - use the source texture + else source = getTempFramebuffer(currentFramebufferIndex)?.texture || null; // All following draw calls use the temp buffer last drawn to + drawCount++; + if (lastInChain && !(flags & DRAW.INTERMEDIATE)) { // Last filter in our chain - draw directly to the WebGL Canvas. We may also have to flip the image vertically now target = null; - flipY = _drawCount % 2 === 0; + flipY = drawCount % 2 === 0; } else { - _currentFramebufferIndex = (_currentFramebufferIndex + 1) % 2; - target = _getTempFramebuffer(_currentFramebufferIndex)?.fbo; // Intermediate draw call - get a temp buffer to draw to + currentFramebufferIndex = (currentFramebufferIndex + 1) % 2; + target = getTempFramebuffer(currentFramebufferIndex)?.fbo || null; // Intermediate draw call - get a temp buffer to draw to } gl.bindTexture(gl.TEXTURE_2D, source); // Bind the source and target and draw the two triangles gl.bindFramebuffer(gl.FRAMEBUFFER, target); - gl.uniform1f(_currentProgram.uniform['flipY'], (flipY ? -1 : 1)); + gl.uniform1f(currentProgram.uniform['flipY'], (flipY ? -1 : 1)); gl.drawArrays(gl.TRIANGLES, 0, 6); - }; + } - this.apply = function (image) { - _resize(image.width, image.height); - _drawCount = 0; - if (!_sourceTexture) _sourceTexture = gl.createTexture(); // Create the texture for the input image if we haven't yet - gl.bindTexture(gl.TEXTURE_2D, _sourceTexture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - if (_filterChain.length === 0) { // draw when done with filters - _draw(); - } else { // apply filters one-by-one recursively - for (let i = 0; i < _filterChain.length; i++) { - _lastInChain = (i === _filterChain.length - 1); - const f = _filterChain[i]; - f.func.apply(this, f.args || []); - } + function compileShader(fragmentSource) { + if (shaderProgramCache[fragmentSource]) { + currentProgram = shaderProgramCache[fragmentSource]; + gl.useProgram(currentProgram?.id || null); + return currentProgram; } - return _canvas; - }; - - const _compileShader = function (fragmentSource) { - if (_shaderProgramCache[fragmentSource]) { - _currentProgram = _shaderProgramCache[fragmentSource]; - gl.useProgram(_currentProgram?.id); - return _currentProgram; - } - _currentProgram = new GLProgram(gl, shaders.vertexIdentity, fragmentSource); + currentProgram = new GLProgram(gl, shaders.vertexIdentity, fragmentSource); const floatSize = Float32Array.BYTES_PER_ELEMENT; const vertSize = 4 * floatSize; - gl.enableVertexAttribArray(_currentProgram.attribute['pos']); - gl.vertexAttribPointer(_currentProgram.attribute['pos'], 2, gl.FLOAT, false, vertSize, 0 * floatSize); - gl.enableVertexAttribArray(_currentProgram.attribute.uv); - gl.vertexAttribPointer(_currentProgram.attribute['uv'], 2, gl.FLOAT, false, vertSize, 2 * floatSize); - _shaderProgramCache[fragmentSource] = _currentProgram; - return _currentProgram; - }; + gl.enableVertexAttribArray(currentProgram.attribute['pos']); + gl.vertexAttribPointer(currentProgram.attribute['pos'], 2, gl.FLOAT, false, vertSize, 0 * floatSize); + gl.enableVertexAttribArray(currentProgram.attribute['uv']); + gl.vertexAttribPointer(currentProgram.attribute['uv'], 2, gl.FLOAT, false, vertSize, 2 * floatSize); + shaderProgramCache[fragmentSource] = currentProgram; + return currentProgram; + } - // Color Matrix Filter: Used by most color filters - const _filter = { - colorMatrix: (matrix) => { - const m = new Float32Array(matrix); // Create a Float32 Array and normalize the offset component to 0-1 + const filter = { + colorMatrix: (matrix) => { // general color matrix filter + const m = new Float32Array(matrix); m[4] /= 255; m[9] /= 255; m[14] /= 255; @@ -185,14 +154,14 @@ export function GLImageFilter(params) { const shader = (m[18] === 1 && m[3] === 0 && m[8] === 0 && m[13] === 0 && m[15] === 0 && m[16] === 0 && m[17] === 0 && m[19] === 0) // Can we ignore the alpha value? Makes things a bit faster. ? shaders.colorMatrixWithoutAlpha : shaders.colorMatrixWithAlpha; - const program = _compileShader(shader); + const program = compileShader(shader); gl.uniform1fv(program?.uniform['m'], m); - _draw(); + draw(); }, brightness: (brightness) => { const b = (brightness || 0) + 1; - _filter.colorMatrix([ + filter.colorMatrix([ b, 0, 0, 0, 0, 0, b, 0, 0, 0, 0, 0, b, 0, 0, @@ -203,7 +172,7 @@ export function GLImageFilter(params) { saturation: (amount) => { const x = (amount || 0) * 2 / 3 + 1; const y = ((x - 1) * -0.5); - _filter.colorMatrix([ + filter.colorMatrix([ x, y, y, 0, 0, y, x, y, 0, 0, y, y, x, 0, 0, @@ -212,13 +181,13 @@ export function GLImageFilter(params) { }, desaturate: () => { - _filter.saturation(-1); + filter.saturation(-1); }, contrast: (amount) => { const v = (amount || 0) + 1; const o = -128 * (v - 1); - _filter.colorMatrix([ + filter.colorMatrix([ v, 0, 0, 0, o, 0, v, 0, 0, o, 0, 0, v, 0, o, @@ -227,7 +196,7 @@ export function GLImageFilter(params) { }, negative: () => { - _filter.contrast(-2); + filter.contrast(-2); }, hue: (rotation) => { @@ -237,7 +206,7 @@ export function GLImageFilter(params) { const lumR = 0.213; const lumG = 0.715; const lumB = 0.072; - _filter.colorMatrix([ + filter.colorMatrix([ lumR + cos * (1 - lumR) + sin * (-lumR), lumG + cos * (-lumG) + sin * (-lumG), lumB + cos * (-lumB) + sin * (1 - lumB), 0, 0, lumR + cos * (-lumR) + sin * (0.143), lumG + cos * (1 - lumG) + sin * (0.140), lumB + cos * (-lumB) + sin * (-0.283), 0, 0, lumR + cos * (-lumR) + sin * (-(1 - lumR)), lumG + cos * (-lumG) + sin * (lumG), lumB + cos * (1 - lumB) + sin * (lumB), 0, 0, @@ -246,7 +215,7 @@ export function GLImageFilter(params) { }, desaturateLuminance: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 0.2764723, 0.9297080, 0.0938197, 0, -37.1, 0.2764723, 0.9297080, 0.0938197, 0, -37.1, 0.2764723, 0.9297080, 0.0938197, 0, -37.1, @@ -255,7 +224,7 @@ export function GLImageFilter(params) { }, sepia: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 0.393, 0.7689999, 0.18899999, 0, 0, 0.349, 0.6859999, 0.16799999, 0, 0, 0.272, 0.5339999, 0.13099999, 0, 0, @@ -264,7 +233,7 @@ export function GLImageFilter(params) { }, brownie: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, @@ -273,7 +242,7 @@ export function GLImageFilter(params) { }, vintagePinhole: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, @@ -282,7 +251,7 @@ export function GLImageFilter(params) { }, kodachrome: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, @@ -291,7 +260,7 @@ export function GLImageFilter(params) { }, technicolor: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, @@ -300,7 +269,7 @@ export function GLImageFilter(params) { }, polaroid: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, @@ -309,7 +278,7 @@ export function GLImageFilter(params) { }, shiftToBGR: () => { - _filter.colorMatrix([ + filter.colorMatrix([ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, @@ -317,19 +286,19 @@ export function GLImageFilter(params) { ]); }, - // Convolution Filter - convolution: (matrix) => { + convolution: (matrix) => { // general convolution Filter const m = new Float32Array(matrix); - const pixelSizeX = 1 / _width; - const pixelSizeY = 1 / _height; - const program = _compileShader(shaders.convolution); + const pixelSizeX = 1 / canvas.width; + const pixelSizeY = 1 / canvas.height; + const program = compileShader(shaders.convolution); gl.uniform1fv(program?.uniform['m'], m); gl.uniform2f(program?.uniform['px'], pixelSizeX, pixelSizeY); - _draw(); + draw(); }, detectEdges: () => { - _filter.convolution.call(this, [ + // @ts-ignore this + filter.convolution.call(this, [ 0, 1, 0, 1, -4, 1, 0, 1, 0, @@ -337,7 +306,8 @@ export function GLImageFilter(params) { }, sobelX: () => { - _filter.convolution.call(this, [ + // @ts-ignore this + filter.convolution.call(this, [ -1, 0, 1, -2, 0, 2, -1, 0, 1, @@ -345,7 +315,8 @@ export function GLImageFilter(params) { }, sobelY: () => { - _filter.convolution.call(this, [ + // @ts-ignore this + filter.convolution.call(this, [ -1, -2, -1, 0, 0, 0, 1, 2, 1, @@ -354,7 +325,8 @@ export function GLImageFilter(params) { sharpen: (amount) => { const a = amount || 1; - _filter.convolution.call(this, [ + // @ts-ignore this + filter.convolution.call(this, [ 0, -1 * a, 0, -1 * a, 1 + 4 * a, -1 * a, 0, -1 * a, 0, @@ -363,33 +335,76 @@ export function GLImageFilter(params) { emboss: (size) => { const s = size || 1; - _filter.convolution.call(this, [ + // @ts-ignore this + filter.convolution.call(this, [ -2 * s, -1 * s, 0, -1 * s, 1, 1 * s, 0, 1 * s, 2 * s, ]); }, - // Blur Filter blur: (size) => { - const blurSizeX = (size / 7) / _width; - const blurSizeY = (size / 7) / _height; - const program = _compileShader(shaders.blur); + const blurSizeX = (size / 7) / canvas.width; + const blurSizeY = (size / 7) / canvas.height; + const program = compileShader(shaders.blur); // Vertical gl.uniform2f(program?.uniform['px'], 0, blurSizeY); - _draw(DRAW.INTERMEDIATE); + draw(DRAW.INTERMEDIATE); // Horizontal gl.uniform2f(program?.uniform['px'], blurSizeX, 0); - _draw(); + draw(); }, - // Pixelate Filter pixelate: (size) => { - const blurSizeX = (size) / _width; - const blurSizeY = (size) / _height; - const program = _compileShader(shaders.pixelate); + const blurSizeX = (size) / canvas.width; + const blurSizeY = (size) / canvas.height; + const program = compileShader(shaders.pixelate); gl.uniform2f(program?.uniform['size'], blurSizeX, blurSizeY); - _draw(); + draw(); }, }; + + // @ts-ignore this + this.add = function (name) { + // eslint-disable-next-line prefer-rest-params + const args = Array.prototype.slice.call(arguments, 1); + const func = filter[name]; + filterChain.push({ func, args }); + }; + + // @ts-ignore this + this.reset = function () { + filterChain = []; + }; + + // @ts-ignore this + this.get = function () { + return filterChain; + }; + + // @ts-ignore this + this.apply = function (image) { + resize(image.width, image.height); + drawCount = 0; + if (!sourceTexture) sourceTexture = gl.createTexture(); // Create the texture for the input image if we haven't yet + gl.bindTexture(gl.TEXTURE_2D, sourceTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + for (let i = 0; i < filterChain.length; i++) { + lastInChain = (i === filterChain.length - 1); + const f = filterChain[i]; + // @ts-ignore function assigment + f.func.apply(this, f.args || []); + } + return canvas; + }; + + // @ts-ignore this + this.draw = function (image) { + this.add('brightness', 0); + return this.apply(image); + }; }