import { simd } from 'wasm-feature-detect';

/**
 * Checks if the current platform is a mobile device.
 *
 * See:
 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
 */
const isMobile = () => /Mobi/i.test(navigator.userAgent);
/**
 * Runs a check to see if the current platform supports
 * the necessary APIs required for the video filters.
 */
const isPlatformSupported = async () => typeof document !== 'undefined' &&
    typeof window !== 'undefined' &&
    typeof navigator !== 'undefined' &&
    !isMobile() && // we don't support mobile devices yet due to performance issues
    typeof WebAssembly !== 'undefined' &&
    !!window.WebGL2RenderingContext && // WebGL2 is required for the video filters
    !!document.createElement('canvas').getContext('webgl2') &&
    (await simd()); // SIMD is required for the wasm module

/**
 * Use it along with boyswan.glsl-literal VSCode extension
 * to get GLSL syntax highlighting.
 * https://marketplace.visualstudio.com/items?itemName=boyswan.glsl-literal
 *
 * On VSCode OSS, boyswan.glsl-literal requires slevesque.shader extension
 * to be installed as well.
 * https://marketplace.visualstudio.com/items?itemName=slevesque.shader
 */
const glsl = String.raw;
function createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer) {
    const program = createProgram(gl, vertexShader, fragmentShader);
    const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
    const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texCoord');
    gl.enableVertexAttribArray(texCoordAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
    return program;
}
function createProgram(gl, vertexShader, fragmentShader) {
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        throw new Error(`Could not link WebGL program: ${gl.getProgramInfoLog(program)}`);
    }
    return program;
}
function compileShader(gl, shaderType, shaderSource) {
    const shader = gl.createShader(shaderType);
    gl.shaderSource(shader, shaderSource);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw new Error(`Could not compile shader: ${gl.getShaderInfoLog(shader)}`);
    }
    return shader;
}
function createTexture(gl, internalformat, width, height, minFilter = gl.NEAREST, magFilter = gl.NEAREST) {
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    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, minFilter);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
    gl.texStorage2D(gl.TEXTURE_2D, 1, internalformat, width, height);
    return texture;
}
async function readPixelsAsync(gl, x, y, width, height, format, type, dest) {
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
    gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ);
    gl.readPixels(x, y, width, height, format, type, 0);
    gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
    await getBufferSubDataAsync(gl, gl.PIXEL_PACK_BUFFER, buf, 0, dest);
    gl.deleteBuffer(buf);
    return dest;
}
async function getBufferSubDataAsync(gl, target, buffer, srcByteOffset, dstBuffer, dstOffset, length) {
    const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
    gl.flush();
    if (!sync)
        return;
    const res = await clientWaitAsync(gl, sync);
    gl.deleteSync(sync);
    if (res !== gl.WAIT_FAILED) {
        gl.bindBuffer(target, buffer);
        gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length);
        gl.bindBuffer(target, null);
    }
}
function clientWaitAsync(gl, sync) {
    return new Promise((resolve) => {
        function test() {
            const res = gl.clientWaitSync(sync, 0, 0);
            if (res === gl.WAIT_FAILED) {
                resolve(res);
                return;
            }
            if (res === gl.TIMEOUT_EXPIRED) {
                setTimeout(test);
                return;
            }
            resolve(res);
        }
        setTimeout(test);
    });
}

function buildBackgroundBlurStage(gl, vertexShader, positionBuffer, texCoordBuffer, personMaskTexture, canvas, blurLevel) {
    const blurPass = buildBlurPass(gl, vertexShader, positionBuffer, texCoordBuffer, personMaskTexture, canvas, blurLevel);
    const blendPass = buildBlendPass(gl, positionBuffer, texCoordBuffer, canvas);
    function render() {
        blurPass.render();
        blendPass.render();
    }
    function updateCoverage(coverage) {
        blendPass.updateCoverage(coverage);
    }
    function cleanUp() {
        blendPass.cleanUp();
        blurPass.cleanUp();
    }
    return {
        render,
        updateCoverage,
        cleanUp,
    };
}
function buildBlurPass(gl, vertexShader, positionBuffer, texCoordBuffer, personMaskTexture, canvas, blurLevel) {
    const sigma = typeof blurLevel === 'number'
        ? blurLevel
        : blurLevel === 'low'
            ? 2
            : blurLevel === 'medium'
                ? 4
                : 6;
    const windowSize = Math.max(1, Math.floor(sigma * 3));
    const offset = new Array(windowSize).fill(0).map((v, index) => index);
    const variance = sigma ** 2;
    const weights = offset.map((x) => {
        const m = sigma * Math.sqrt(2 * Math.PI);
        const e = Math.exp(-(x ** 2) / (2 * variance));
        return e / m;
    });
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;

    uniform sampler2D u_inputFrame;
    uniform sampler2D u_personMask;
    uniform vec2 u_texelSize;

    in vec2 v_texCoord;
    out vec4 outColor;

    const float offset[${windowSize}] = float[](${offset.map((i) => i.toFixed(10)).join(', ')});
    const float weight[${windowSize}] = float[](${weights.map((i) => i.toFixed(10)).join(', ')});

    void main() {
      vec4 centerColor = texture(u_inputFrame, v_texCoord);
      float personMask = texture(u_personMask, v_texCoord).a;

      vec4 frameColor = centerColor * weight[0] * (1.0 - personMask);

      for (int i = 1; i < ${windowSize}; i++) {
        vec2 offset = vec2(offset[i]) * u_texelSize;

        vec2 texCoord = v_texCoord + offset;
        frameColor += texture(u_inputFrame, texCoord)
           * weight[i]
           * (1.0 - texture(u_personMask, texCoord).a);

        texCoord = v_texCoord - offset;
        frameColor += texture(u_inputFrame, texCoord)
          * weight[i]
          * (1.0 - texture(u_personMask, texCoord).a);
      }
      outColor = vec4(frameColor.rgb + (1.0 - frameColor.a) * centerColor.rgb, 1.0);
    }
  `;
    const scale = 0.5;
    const outputWidth = canvas.width * scale;
    const outputHeight = canvas.height * scale;
    const texelWidth = 1 / outputWidth;
    const texelHeight = 1 / outputHeight;
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
    const personMaskLocation = gl.getUniformLocation(program, 'u_personMask');
    const texelSizeLocation = gl.getUniformLocation(program, 'u_texelSize');
    const texture1 = createTexture(gl, gl.RGBA8, outputWidth, outputHeight, gl.NEAREST, 
    // @ts-expect-error types are incomplete
    gl.LINEAR);
    const texture2 = createTexture(gl, gl.RGBA8, outputWidth, outputHeight, gl.NEAREST, 
    // @ts-expect-error types are incomplete
    gl.LINEAR);
    const frameBuffer1 = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer1);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture1, 0);
    const frameBuffer2 = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer2);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture2, 0);
    gl.useProgram(program);
    gl.uniform1i(personMaskLocation, 1);
    function render() {
        gl.viewport(0, 0, outputWidth, outputHeight);
        gl.useProgram(program);
        gl.uniform1i(inputFrameLocation, 0);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, personMaskTexture);
        for (let i = 0; i < 3; i++) {
            gl.uniform2f(texelSizeLocation, 0, texelHeight);
            gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer1);
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            gl.activeTexture(gl.TEXTURE2);
            gl.bindTexture(gl.TEXTURE_2D, texture1);
            gl.uniform1i(inputFrameLocation, 2);
            gl.uniform2f(texelSizeLocation, texelWidth, 0);
            gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer2);
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            gl.bindTexture(gl.TEXTURE_2D, texture2);
        }
    }
    function cleanUp() {
        gl.deleteFramebuffer(frameBuffer2);
        gl.deleteFramebuffer(frameBuffer1);
        gl.deleteTexture(texture2);
        gl.deleteTexture(texture1);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
    }
    return {
        render,
        cleanUp,
    };
}
function buildBlendPass(gl, positionBuffer, texCoordBuffer, canvas) {
    const vertexShaderSource = glsl `#version 300 es

    in vec2 a_position;
    in vec2 a_texCoord;

    out vec2 v_texCoord;

    void main() {
      // Flipping Y is required when rendering to canvas
      gl_Position = vec4(a_position * vec2(1.0, -1.0), 0.0, 1.0);
      v_texCoord = a_texCoord;
    }
  `;
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;

    uniform sampler2D u_inputFrame;
    uniform sampler2D u_personMask;
    uniform sampler2D u_blurredInputFrame;
    uniform vec2 u_coverage;

    in vec2 v_texCoord;

    out vec4 outColor;

    void main() {
      vec3 color = texture(u_inputFrame, v_texCoord).rgb;
      vec3 blurredColor = texture(u_blurredInputFrame, v_texCoord).rgb;
      float personMask = texture(u_personMask, v_texCoord).a;
      personMask = smoothstep(u_coverage.x, u_coverage.y, personMask);
      outColor = vec4(mix(blurredColor, color, personMask), 1.0);
    }
  `;
    const { width: outputWidth, height: outputHeight } = canvas;
    const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
    const personMaskLocation = gl.getUniformLocation(program, 'u_personMask');
    const blurredInputFrame = gl.getUniformLocation(program, 'u_blurredInputFrame');
    const coverageLocation = gl.getUniformLocation(program, 'u_coverage');
    gl.useProgram(program);
    gl.uniform1i(inputFrameLocation, 0);
    gl.uniform1i(personMaskLocation, 1);
    gl.uniform1i(blurredInputFrame, 2);
    gl.uniform2f(coverageLocation, 0, 1);
    function render() {
        gl.viewport(0, 0, outputWidth, outputHeight);
        gl.useProgram(program);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
    function updateCoverage(coverage) {
        gl.useProgram(program);
        gl.uniform2f(coverageLocation, coverage[0], coverage[1]);
    }
    function cleanUp() {
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
        gl.deleteShader(vertexShader);
    }
    return {
        render,
        updateCoverage,
        cleanUp,
    };
}

function buildBackgroundImageStage(gl, positionBuffer, texCoordBuffer, personMaskTexture, backgroundImage, canvas) {
    const vertexShaderSource = glsl `#version 300 es

    uniform vec2 u_backgroundScale;
    uniform vec2 u_backgroundOffset;

    in vec2 a_position;
    in vec2 a_texCoord;

    out vec2 v_texCoord;
    out vec2 v_backgroundCoord;

    void main() {
      // Flipping Y is required when rendering to canvas
      gl_Position = vec4(a_position * vec2(1.0, -1.0), 0.0, 1.0);
      v_texCoord = a_texCoord;
      v_backgroundCoord = a_texCoord * u_backgroundScale + u_backgroundOffset;
    }
  `;
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;

    uniform sampler2D u_inputFrame;
    uniform sampler2D u_personMask;
    uniform sampler2D u_background;
    uniform vec2 u_coverage;
    uniform float u_lightWrapping;
    uniform float u_blendMode;

    in vec2 v_texCoord;
    in vec2 v_backgroundCoord;

    out vec4 outColor;

    vec3 screen(vec3 a, vec3 b) {
      return 1.0 - (1.0 - a) * (1.0 - b);
    }

    vec3 linearDodge(vec3 a, vec3 b) {
      return a + b;
    }

    void main() {
      vec3 frameColor = texture(u_inputFrame, v_texCoord).rgb;
      vec3 backgroundColor = texture(u_background, v_backgroundCoord).rgb;
      float personMask = texture(u_personMask, v_texCoord).a;
      float lightWrapMask = 1.0 - max(0.0, personMask - u_coverage.y) / (1.0 - u_coverage.y);
      vec3 lightWrap = u_lightWrapping * lightWrapMask * backgroundColor;

      frameColor = u_blendMode * linearDodge(frameColor, lightWrap)
        + (1.0 - u_blendMode) * screen(frameColor, lightWrap);
      personMask = smoothstep(u_coverage.x, u_coverage.y, personMask);
      outColor = vec4(frameColor * personMask + backgroundColor * (1.0 - personMask), 1.0);
    }
  `;
    const { width: outputWidth, height: outputHeight } = canvas;
    const outputRatio = outputWidth / outputHeight;
    const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const backgroundScaleLocation = gl.getUniformLocation(program, 'u_backgroundScale');
    const backgroundOffsetLocation = gl.getUniformLocation(program, 'u_backgroundOffset');
    const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
    const personMaskLocation = gl.getUniformLocation(program, 'u_personMask');
    const backgroundLocation = gl.getUniformLocation(program, 'u_background');
    const coverageLocation = gl.getUniformLocation(program, 'u_coverage');
    const lightWrappingLocation = gl.getUniformLocation(program, 'u_lightWrapping');
    const blendModeLocation = gl.getUniformLocation(program, 'u_blendMode');
    gl.useProgram(program);
    gl.uniform2f(backgroundScaleLocation, 1, 1);
    gl.uniform2f(backgroundOffsetLocation, 0, 0);
    gl.uniform1i(inputFrameLocation, 0);
    gl.uniform1i(personMaskLocation, 1);
    gl.uniform2f(coverageLocation, 0, 1);
    gl.uniform1f(lightWrappingLocation, 0);
    gl.uniform1f(blendModeLocation, 0);
    let backgroundTexture = null;
    // TODO Find a better to handle background being loaded
    if (backgroundImage?.complete) {
        updateBackgroundImage(backgroundImage);
    }
    else if (backgroundImage) {
        backgroundImage.onload = () => {
            updateBackgroundImage(backgroundImage);
        };
    }
    function render() {
        gl.viewport(0, 0, outputWidth, outputHeight);
        gl.useProgram(program);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, personMaskTexture);
        if (backgroundTexture !== null) {
            gl.activeTexture(gl.TEXTURE2);
            gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);
            // TODO Handle correctly the background not loaded yet
            gl.uniform1i(backgroundLocation, 2);
        }
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
    function updateBackgroundImage(bgImage) {
        backgroundTexture = createTexture(gl, gl.RGBA8, bgImage.naturalWidth, bgImage.naturalHeight, 
        // @ts-expect-error types are incomplete
        gl.LINEAR, gl.LINEAR);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, bgImage.naturalWidth, bgImage.naturalHeight, gl.RGBA, gl.UNSIGNED_BYTE, bgImage);
        let xOffset = 0;
        let yOffset = 0;
        let backgroundWidth = bgImage.naturalWidth;
        let backgroundHeight = bgImage.naturalHeight;
        const backgroundRatio = backgroundWidth / backgroundHeight;
        if (backgroundRatio < outputRatio) {
            backgroundHeight = backgroundWidth / outputRatio;
            yOffset = (bgImage.naturalHeight - backgroundHeight) / 2;
        }
        else {
            backgroundWidth = backgroundHeight * outputRatio;
            xOffset = (bgImage.naturalWidth - backgroundWidth) / 2;
        }
        const xScale = backgroundWidth / bgImage.naturalWidth;
        const yScale = backgroundHeight / bgImage.naturalHeight;
        xOffset /= bgImage.naturalWidth;
        yOffset /= bgImage.naturalHeight;
        gl.uniform2f(backgroundScaleLocation, xScale, yScale);
        gl.uniform2f(backgroundOffsetLocation, xOffset, yOffset);
    }
    function updateCoverage(coverage) {
        gl.useProgram(program);
        gl.uniform2f(coverageLocation, coverage[0], coverage[1]);
    }
    function updateLightWrapping(lightWrapping) {
        gl.useProgram(program);
        gl.uniform1f(lightWrappingLocation, lightWrapping);
    }
    function updateBlendMode(blendMode) {
        gl.useProgram(program);
        gl.uniform1f(blendModeLocation, blendMode === 'screen' ? 0 : 1);
    }
    function cleanUp() {
        gl.deleteTexture(backgroundTexture);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
        gl.deleteShader(vertexShader);
    }
    return {
        render,
        updateCoverage,
        updateLightWrapping,
        updateBlendMode,
        cleanUp,
    };
}

function buildJointBilateralFilterStage(gl, vertexShader, positionBuffer, texCoordBuffer, inputTexture, outputTexture, canvas, segmentationConfig) {
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;

    uniform sampler2D u_inputFrame;
    uniform sampler2D u_segmentationMask;
    uniform vec2 u_texelSize;
    uniform float u_step;
    uniform float u_radius;
    uniform float u_offset;
    uniform float u_sigmaTexel;
    uniform float u_sigmaColor;

    in vec2 v_texCoord;
    out vec4 outColor;

    float gaussian(float x, float sigma) {
      float coeff = -0.5 / (sigma * sigma * 4.0 + 1.0e-6);
      return exp((x * x) * coeff);
    }

    void main() {
      vec2 centerCoord = v_texCoord;
      vec3 centerColor = texture(u_inputFrame, centerCoord).rgb;
      float newVal = 0.0;

      float spaceWeight = 0.0;
      float colorWeight = 0.0;
      float totalWeight = 0.0;

      // Subsample kernel space.
      for (float i = -u_radius + u_offset; i <= u_radius; i += u_step) {
        for (float j = -u_radius + u_offset; j <= u_radius; j += u_step) {
          vec2 shift = vec2(j, i) * u_texelSize;
          vec2 coord = vec2(centerCoord + shift);
          vec3 frameColor = texture(u_inputFrame, coord).rgb;
          float outVal = texture(u_segmentationMask, coord).a;

          spaceWeight = gaussian(distance(centerCoord, coord), u_sigmaTexel);
          colorWeight = gaussian(distance(centerColor, frameColor), u_sigmaColor);
          totalWeight += spaceWeight * colorWeight;

          newVal += spaceWeight * colorWeight * outVal;
        }
      }
      newVal /= totalWeight;

      outColor = vec4(vec3(0.0), newVal);
    }
  `;
    const { width: segmentationWidth, height: segmentationHeight } = segmentationConfig;
    const { width: outputWidth, height: outputHeight } = canvas;
    const texelWidth = 1 / outputWidth;
    const texelHeight = 1 / outputHeight;
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
    const segmentationMaskLocation = gl.getUniformLocation(program, 'u_segmentationMask');
    const texelSizeLocation = gl.getUniformLocation(program, 'u_texelSize');
    const stepLocation = gl.getUniformLocation(program, 'u_step');
    const radiusLocation = gl.getUniformLocation(program, 'u_radius');
    const offsetLocation = gl.getUniformLocation(program, 'u_offset');
    const sigmaTexelLocation = gl.getUniformLocation(program, 'u_sigmaTexel');
    const sigmaColorLocation = gl.getUniformLocation(program, 'u_sigmaColor');
    const frameBuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
    gl.useProgram(program);
    gl.uniform1i(inputFrameLocation, 0);
    gl.uniform1i(segmentationMaskLocation, 1);
    gl.uniform2f(texelSizeLocation, texelWidth, texelHeight);
    // Ensures default values are configured to prevent infinite
    // loop in fragment shader
    updateSigmaSpace(0);
    updateSigmaColor(0);
    function render() {
        gl.viewport(0, 0, outputWidth, outputHeight);
        gl.useProgram(program);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, inputTexture);
        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
    function updateSigmaSpace(sigmaSpace) {
        sigmaSpace *= Math.max(outputWidth / segmentationWidth, outputHeight / segmentationHeight);
        const kSparsityFactor = 0.66; // Higher is sparser.
        const step = Math.max(1, Math.sqrt(sigmaSpace) * kSparsityFactor);
        const radius = sigmaSpace;
        const offset = step > 1 ? step * 0.5 : 0;
        const sigmaTexel = Math.max(texelWidth, texelHeight) * sigmaSpace;
        gl.useProgram(program);
        gl.uniform1f(stepLocation, step);
        gl.uniform1f(radiusLocation, radius);
        gl.uniform1f(offsetLocation, offset);
        gl.uniform1f(sigmaTexelLocation, sigmaTexel);
    }
    function updateSigmaColor(sigmaColor) {
        gl.useProgram(program);
        gl.uniform1f(sigmaColorLocation, sigmaColor);
    }
    function cleanUp() {
        gl.deleteFramebuffer(frameBuffer);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
    }
    return { render, updateSigmaSpace, updateSigmaColor, cleanUp };
}

function buildResizingStage(gl, vertexShader, positionBuffer, texCoordBuffer, tflite, segmentationConfig, onError) {
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;
    uniform sampler2D u_inputFrame;
    in vec2 v_texCoord;
    out vec4 outColor;

    void main() {
      outColor = texture(u_inputFrame, v_texCoord);
    }
  `;
    // TFLite memory will be accessed as float32
    const tfliteInputMemoryOffset = tflite._getInputMemoryOffset() / 4;
    const { width: outputWidth, height: outputHeight } = segmentationConfig;
    const outputPixelCount = outputWidth * outputHeight;
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const inputFrameLocation = gl.getUniformLocation(program, 'u_inputFrame');
    const outputTexture = createTexture(gl, gl.RGBA8, outputWidth, outputHeight);
    const frameBuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
    const outputPixels = new Uint8Array(outputPixelCount * 4);
    gl.useProgram(program);
    gl.uniform1i(inputFrameLocation, 0);
    function render() {
        gl.viewport(0, 0, outputWidth, outputHeight);
        gl.useProgram(program);
        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        // Downloads pixels asynchronously from GPU while rendering the current frame.
        // The pixels will be available in the next frame render which results
        // in offsets in the segmentation output but increases the frame rate.
        readPixelsAsync(gl, 0, 0, outputWidth, outputHeight, gl.RGBA, gl.UNSIGNED_BYTE, outputPixels).catch((error) => {
        });
        for (let i = 0; i < outputPixelCount; i++) {
            const tfliteIndex = tfliteInputMemoryOffset + i * 3;
            const outputIndex = i * 4;
            tflite.HEAPF32[tfliteIndex] = outputPixels[outputIndex] / 255;
            tflite.HEAPF32[tfliteIndex + 1] = outputPixels[outputIndex + 1] / 255;
            tflite.HEAPF32[tfliteIndex + 2] = outputPixels[outputIndex + 2] / 255;
        }
    }
    function cleanUp() {
        gl.deleteFramebuffer(frameBuffer);
        gl.deleteTexture(outputTexture);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
    }
    return { render, cleanUp };
}

function buildSoftmaxStage(gl, vertexShader, positionBuffer, texCoordBuffer, tflite, outputTexture, segmentationConfig) {
    const fragmentShaderSource = glsl `#version 300 es

    precision highp float;

    uniform sampler2D u_inputSegmentation;
    in vec2 v_texCoord;
    out vec4 outColor;

    void main() {
      vec2 segmentation = texture(u_inputSegmentation, v_texCoord).rg;
      float shift = max(segmentation.r, segmentation.g);
      float backgroundExp = exp(segmentation.r - shift);
      float personExp = exp(segmentation.g - shift);
      outColor = vec4(vec3(0.0), personExp / (backgroundExp + personExp));
    }
  `;
    // TFLite memory will be accessed as float32
    const tfliteOutputMemoryOffset = tflite._getOutputMemoryOffset() / 4;
    const { width: segmentationWidth, height: segmentationHeight } = segmentationConfig;
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createPipelineStageProgram(gl, vertexShader, fragmentShader, positionBuffer, texCoordBuffer);
    const inputLocation = gl.getUniformLocation(program, 'u_inputSegmentation');
    const inputTexture = createTexture(gl, gl.RG32F, segmentationWidth, segmentationHeight);
    const frameBuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
    gl.useProgram(program);
    gl.uniform1i(inputLocation, 1);
    function render() {
        gl.viewport(0, 0, segmentationWidth, segmentationHeight);
        gl.useProgram(program);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, inputTexture);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, segmentationWidth, segmentationHeight, gl.RG, gl.FLOAT, tflite.HEAPF32, tfliteOutputMemoryOffset);
        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
    function cleanUp() {
        gl.deleteFramebuffer(frameBuffer);
        gl.deleteTexture(inputTexture);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
    }
    return { render, cleanUp };
}

function buildWebGL2Pipeline(videoSource, backgroundImage, blurLevel, backgroundFilter, canvas, tflite, segmentationConfig, onError) {
    const gl = canvas.getContext('webgl2');
    if (!gl)
        throw new Error('WebGL2 is not supported');
    if (gl.isContextLost())
        throw new Error('WebGL2 context was lost');
    const { width: frameWidth, height: frameHeight } = videoSource;
    const { width: segmentationWidth, height: segmentationHeight } = segmentationConfig;
    const vertexShaderSource = glsl `#version 300 es

    in vec2 a_position;
    in vec2 a_texCoord;
    out vec2 v_texCoord;

    void main() {
      gl_Position = vec4(a_position, 0.0, 1.0);
      v_texCoord = a_texCoord;
    }
  `;
    const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const vertexArray = gl.createVertexArray();
    gl.bindVertexArray(vertexArray);
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1.0, -1, -1, 1.0, 1.0, 1.0]), gl.STATIC_DRAW);
    const texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]), gl.STATIC_DRAW);
    // We don't use texStorage2D here because texImage2D seems faster
    // to upload video texture than texSubImage2D even though the latter
    // is supposed to be the recommended way:
    // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#use_texstorage_to_create_textures
    const inputFrameTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, inputFrameTexture);
    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);
    // TODO Rename segmentation and person mask to be more specific
    const segmentationTexture = createTexture(gl, gl.RGBA8, segmentationWidth, segmentationHeight);
    const personMaskTexture = createTexture(gl, gl.RGBA8, frameWidth, frameHeight);
    const resizingStage = buildResizingStage(gl, vertexShader, positionBuffer, texCoordBuffer, tflite, segmentationConfig);
    const loadSegmentationStage = buildSoftmaxStage(gl, vertexShader, positionBuffer, texCoordBuffer, tflite, segmentationTexture, segmentationConfig);
    const jointBilateralFilterStage = buildJointBilateralFilterStage(gl, vertexShader, positionBuffer, texCoordBuffer, segmentationTexture, personMaskTexture, canvas, segmentationConfig);
    const backgroundStage = backgroundFilter === 'blur'
        ? buildBackgroundBlurStage(gl, vertexShader, positionBuffer, texCoordBuffer, personMaskTexture, canvas, blurLevel || 'high')
        : buildBackgroundImageStage(gl, positionBuffer, texCoordBuffer, personMaskTexture, backgroundImage, canvas);
    function render() {
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, inputFrameTexture);
        // texImage2D seems faster than texSubImage2D to upload
        // video texture
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, videoSource);
        gl.bindVertexArray(vertexArray);
        resizingStage.render();
        tflite._runInference();
        loadSegmentationStage.render();
        jointBilateralFilterStage.render();
        backgroundStage.render();
    }
    function updatePostProcessingConfig() {
        jointBilateralFilterStage.updateSigmaSpace(1);
        jointBilateralFilterStage.updateSigmaColor(0.1);
        if (backgroundFilter === 'image') {
            const backgroundImageStage = backgroundStage;
            backgroundImageStage.updateCoverage([0.5, 0.75]);
            backgroundImageStage.updateLightWrapping(0.3);
            backgroundImageStage.updateBlendMode('screen');
        }
        else if (backgroundFilter === 'blur') {
            const backgroundBlurStage = backgroundStage;
            backgroundBlurStage.updateCoverage([0.5, 0.75]);
        }
        else {
            // TODO Handle no background in a separate pipeline path
            const backgroundImageStage = backgroundStage;
            backgroundImageStage.updateCoverage([0, 0.9999]);
            backgroundImageStage.updateLightWrapping(0);
        }
    }
    function cleanUp() {
        backgroundStage.cleanUp();
        jointBilateralFilterStage.cleanUp();
        loadSegmentationStage.cleanUp();
        resizingStage.cleanUp();
        gl.deleteTexture(personMaskTexture);
        gl.deleteTexture(segmentationTexture);
        gl.deleteTexture(inputFrameTexture);
        gl.deleteBuffer(texCoordBuffer);
        gl.deleteBuffer(positionBuffer);
        gl.deleteVertexArray(vertexArray);
        gl.deleteShader(vertexShader);
    }
    return { render, updatePostProcessingConfig, cleanUp };
}

var SegmentationLevel;
(function (SegmentationLevel) {
    SegmentationLevel["LOW"] = "low";
    SegmentationLevel["HIGH"] = "high";
})(SegmentationLevel || (SegmentationLevel = {}));
const getSegmentationParams = (level) => {
    if (level === SegmentationLevel.HIGH) {
        return { width: 256, height: 144 };
    }
    return { width: 160, height: 96 };
};

function createRenderer(tflite, videoSource, targetCanvas, options, onError) {
    const { backgroundFilter, backgroundImage, backgroundBlurLevel, segmentationLevel = SegmentationLevel.HIGH, fps = 30, } = options;
    if (backgroundFilter === 'image' && !backgroundImage) {
        throw new Error(`backgroundImage element is required when backgroundFilter is image`);
    }
    const pipeline = buildWebGL2Pipeline(videoSource, backgroundImage, backgroundBlurLevel, backgroundFilter, targetCanvas, tflite, getSegmentationParams(segmentationLevel));
    const id = setInterval(() => {
        try {
            pipeline.render();
            if (backgroundFilter === 'image') {
                pipeline.updatePostProcessingConfig();
            }
        }
        catch (error) {
            onError?.(error);
        }
    }, 1000 / (fps <= 0 ? 30 : fps));
    return {
        dispose: () => {
            pipeline.cleanUp();
            clearInterval(id);
        },
    };
}

const createTFLiteSIMDModule = (__Module) => {
  __Module = __Module || {};

  var _scriptDir =
    typeof document !== 'undefined' && document.currentScript
      ? document.currentScript.src
      : undefined;

  var Module = typeof __Module != 'undefined' ? __Module : {};
  var readyPromiseResolve, readyPromiseReject;
  Module['ready'] = new Promise(function (resolve, reject) {
    readyPromiseResolve = resolve;
    readyPromiseReject = reject;
  });
  var moduleOverrides = Object.assign({}, Module);
  var thisProgram = './this.program';
  var quit_ = (status, toThrow) => {
    throw toThrow;
  };
  var ENVIRONMENT_IS_WEB = true;
  var scriptDirectory = '';

  function locateFile(path) {
    if (Module['locateFile']) {
      return Module['locateFile'](path, scriptDirectory);
    }
    return scriptDirectory + path;
  }

  var readBinary;
  {
    if (typeof document != 'undefined' && document.currentScript) {
      scriptDirectory = document.currentScript.src;
    }
    if (_scriptDir) {
      scriptDirectory = _scriptDir;
    }
    if (scriptDirectory.indexOf('blob:') !== 0) {
      scriptDirectory = scriptDirectory.substr(
        0,
        scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/') + 1,
      );
    } else {
      scriptDirectory = '';
    }
  }
  var out = Module['print'] || console.log.bind(console);
  var err = Module['printErr'] || console.warn.bind(console);
  Object.assign(Module, moduleOverrides);
  moduleOverrides = null;
  if (Module['arguments']) Module['arguments'];
  if (Module['thisProgram']) thisProgram = Module['thisProgram'];
  if (Module['quit']) quit_ = Module['quit'];
  var wasmBinary;
  if (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];
  Module['noExitRuntime'] || true;
  if (typeof WebAssembly != 'object') {
    abort('no native wasm support detected');
  }
  var wasmMemory;
  var ABORT = false;

  var UTF8Decoder =
    typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined;

  function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) {
    var endIdx = idx + maxBytesToRead;
    var endPtr = idx;
    while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
    if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
      return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
    }
    var str = '';
    while (idx < endPtr) {
      var u0 = heapOrArray[idx++];
      if (!(u0 & 128)) {
        str += String.fromCharCode(u0);
        continue;
      }
      var u1 = heapOrArray[idx++] & 63;
      if ((u0 & 224) == 192) {
        str += String.fromCharCode(((u0 & 31) << 6) | u1);
        continue;
      }
      var u2 = heapOrArray[idx++] & 63;
      if ((u0 & 240) == 224) {
        u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
      } else {
        u0 =
          ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);
      }
      if (u0 < 65536) {
        str += String.fromCharCode(u0);
      } else {
        var ch = u0 - 65536;
        str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
      }
    }
    return str;
  }

  var buffer, HEAP8, HEAPU8, HEAPU32;

  function updateGlobalBufferAndViews(buf) {
    buffer = buf;
    Module['HEAP8'] = HEAP8 = new Int8Array(buf);
    Module['HEAP16'] = new Int16Array(buf);
    Module['HEAP32'] = new Int32Array(buf);
    Module['HEAPU8'] = HEAPU8 = new Uint8Array(buf);
    Module['HEAPU16'] = new Uint16Array(buf);
    Module['HEAPU32'] = HEAPU32 = new Uint32Array(buf);
    Module['HEAPF32'] = new Float32Array(buf);
    Module['HEAPF64'] = new Float64Array(buf);
  }

  Module['INITIAL_MEMORY'] || 16777216;
  var __ATPRERUN__ = [];
  var __ATINIT__ = [];
  var __ATPOSTRUN__ = [];

  function preRun() {
    if (Module['preRun']) {
      if (typeof Module['preRun'] == 'function')
        Module['preRun'] = [Module['preRun']];
      while (Module['preRun'].length) {
        addOnPreRun(Module['preRun'].shift());
      }
    }
    callRuntimeCallbacks(__ATPRERUN__);
  }

  function initRuntime() {
    callRuntimeCallbacks(__ATINIT__);
  }

  function postRun() {
    if (Module['postRun']) {
      if (typeof Module['postRun'] == 'function')
        Module['postRun'] = [Module['postRun']];
      while (Module['postRun'].length) {
        addOnPostRun(Module['postRun'].shift());
      }
    }
    callRuntimeCallbacks(__ATPOSTRUN__);
  }

  function addOnPreRun(cb) {
    __ATPRERUN__.unshift(cb);
  }

  function addOnInit(cb) {
    __ATINIT__.unshift(cb);
  }

  function addOnPostRun(cb) {
    __ATPOSTRUN__.unshift(cb);
  }

  var runDependencies = 0;
  var dependenciesFulfilled = null;

  function addRunDependency(id) {
    runDependencies++;
    if (Module['monitorRunDependencies']) {
      Module['monitorRunDependencies'](runDependencies);
    }
  }

  function removeRunDependency(id) {
    runDependencies--;
    if (Module['monitorRunDependencies']) {
      Module['monitorRunDependencies'](runDependencies);
    }
    if (runDependencies == 0) {
      if (dependenciesFulfilled) {
        var callback = dependenciesFulfilled;
        dependenciesFulfilled = null;
        callback();
      }
    }
  }

  function abort(what) {
    {
      if (Module['onAbort']) {
        Module['onAbort'](what);
      }
    }
    what = 'Aborted(' + what + ')';
    err(what);
    ABORT = true;
    what += '. Build with -sASSERTIONS for more info.';
    var e = new WebAssembly.RuntimeError(what);
    readyPromiseReject(e);
    throw e;
  }

  var dataURIPrefix = 'data:application/octet-stream;base64,';

  function isDataURI(filename) {
    return filename.startsWith(dataURIPrefix);
  }

  var wasmBinaryFile;
  wasmBinaryFile = 'tflite-simd.wasm';
  if (!isDataURI(wasmBinaryFile)) {
    wasmBinaryFile = locateFile(wasmBinaryFile);
  }

  function getBinary(file) {
    try {
      if (file == wasmBinaryFile && wasmBinary) {
        return new Uint8Array(wasmBinary);
      }
      if (readBinary) ;
      throw 'both async and sync fetching of the wasm failed';
      // eslint-disable-next-line @typescript-eslint/no-shadow
    } catch (err) {
      abort(err);
    }
  }

  function getBinaryPromise() {
    if (!wasmBinary && (ENVIRONMENT_IS_WEB)) {
      if (typeof fetch == 'function') {
        return fetch(wasmBinaryFile, { credentials: 'same-origin' })
          .then(function (response) {
            if (!response['ok']) {
              throw (
                "failed to load wasm binary file at '" + wasmBinaryFile + "'"
              );
            }
            return response['arrayBuffer']();
          })
          .catch(function () {
            return getBinary(wasmBinaryFile);
          });
      }
    }
    return Promise.resolve().then(function () {
      return getBinary(wasmBinaryFile);
    });
  }

  function createWasm() {
    var info = {
      env: asmLibraryArg,
      wasi_snapshot_preview1: asmLibraryArg,
    };

    function receiveInstance(instance, module) {
      var exports = instance.exports;
      Module['asm'] = exports;
      wasmMemory = Module['asm']['memory'];
      updateGlobalBufferAndViews(wasmMemory.buffer);
      Module['asm']['__indirect_function_table'];
      addOnInit(Module['asm']['__wasm_call_ctors']);
      removeRunDependency();
    }

    addRunDependency();

    function receiveInstantiationResult(result) {
      receiveInstance(result['instance']);
    }

    function instantiateArrayBuffer(receiver) {
      return getBinaryPromise()
        .then(function (binary) {
          return WebAssembly.instantiate(binary, info);
        })
        .then(function (instance) {
          return instance;
        })
        .then(receiver, function (reason) {
          err('failed to asynchronously prepare wasm: ' + reason);
          abort(reason);
        });
    }

    function instantiateAsync() {
      if (
        !wasmBinary &&
        typeof WebAssembly.instantiateStreaming == 'function' &&
        !isDataURI(wasmBinaryFile) &&
        typeof fetch == 'function'
      ) {
        return fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(
          function (response) {
            var result = WebAssembly.instantiateStreaming(response, info);
            return result.then(receiveInstantiationResult, function (reason) {
              err('wasm streaming compile failed: ' + reason);
              err('falling back to ArrayBuffer instantiation');
              return instantiateArrayBuffer(receiveInstantiationResult);
            });
          },
        );
      } else {
        return instantiateArrayBuffer(receiveInstantiationResult);
      }
    }

    if (Module['instantiateWasm']) {
      try {
        var exports = Module['instantiateWasm'](info, receiveInstance);
        return exports;
      } catch (e) {
        err('Module.instantiateWasm callback failed with error: ' + e);
        readyPromiseReject(e);
      }
    }
    instantiateAsync().catch(readyPromiseReject);
    return {};
  }

  function ExitStatus(status) {
    this.name = 'ExitStatus';
    this.message = 'Program terminated with exit(' + status + ')';
    this.status = status;
  }

  function callRuntimeCallbacks(callbacks) {
    while (callbacks.length > 0) {
      callbacks.shift()(Module);
    }
  }

  function __dlinit(main_dso_handle) {}

  var dlopenMissingError =
    'To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking';

  function __dlopen_js(filename, flag) {
    abort(dlopenMissingError);
  }

  function __dlsym_js(handle, symbol) {
    abort(dlopenMissingError);
  }

  var nowIsMonotonic = true;

  function __emscripten_get_now_is_monotonic() {
    return nowIsMonotonic;
  }

  function __mmap_js(len, prot, flags, fd, off, allocated) {
    return -52;
  }

  function __munmap_js(addr, len, prot, flags, fd, offset) {}

  function _abort() {
    abort('');
  }

  function _emscripten_date_now() {
    return Date.now();
  }

  function getHeapMax() {
    return 2147483648;
  }

  function _emscripten_get_heap_max() {
    return getHeapMax();
  }

  var _emscripten_get_now;
  _emscripten_get_now = () => performance.now();

  function _emscripten_memcpy_big(dest, src, num) {
    HEAPU8.copyWithin(dest, src, src + num);
  }

  function emscripten_realloc_buffer(size) {
    try {
      wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
      updateGlobalBufferAndViews(wasmMemory.buffer);
      return 1;
    } catch (e) {}
  }

  function _emscripten_resize_heap(requestedSize) {
    var oldSize = HEAPU8.length;
    requestedSize = requestedSize >>> 0;
    var maxHeapSize = getHeapMax();
    if (requestedSize > maxHeapSize) {
      return false;
    }
    let alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);
    for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
      var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
      overGrownHeapSize = Math.min(
        overGrownHeapSize,
        requestedSize + 100663296,
      );
      var newSize = Math.min(
        maxHeapSize,
        alignUp(Math.max(requestedSize, overGrownHeapSize), 65536),
      );
      var replacement = emscripten_realloc_buffer(newSize);
      if (replacement) {
        return true;
      }
    }
    return false;
  }

  var ENV = {};

  function getExecutableName() {
    return thisProgram || './this.program';
  }

  function getEnvStrings() {
    if (!getEnvStrings.strings) {
      var lang =
        (
          (typeof navigator == 'object' &&
            navigator.languages &&
            navigator.languages[0]) ||
          'C'
        ).replace('-', '_') + '.UTF-8';
      var env = {
        USER: 'web_user',
        LOGNAME: 'web_user',
        PATH: '/',
        PWD: '/',
        HOME: '/home/web_user',
        LANG: lang,
        _: getExecutableName(),
      };
      for (var x in ENV) {
        if (ENV[x] === undefined) delete env[x];
        else env[x] = ENV[x];
      }
      var strings = [];
      for (var x in env) {
        strings.push(x + '=' + env[x]);
      }
      getEnvStrings.strings = strings;
    }
    return getEnvStrings.strings;
  }

  // eslint-disable-next-line @typescript-eslint/no-shadow
  function writeAsciiToMemory(str, buffer, dontAddNull) {
    for (var i = 0; i < str.length; ++i) {
      HEAP8[buffer++ >> 0] = str.charCodeAt(i);
    }
    HEAP8[buffer >> 0] = 0;
  }

  function _environ_get(__environ, environ_buf) {
    var bufSize = 0;
    getEnvStrings().forEach(function (string, i) {
      var ptr = environ_buf + bufSize;
      HEAPU32[(__environ + i * 4) >> 2] = ptr;
      writeAsciiToMemory(string, ptr);
      bufSize += string.length + 1;
    });
    return 0;
  }

  function _environ_sizes_get(penviron_count, penviron_buf_size) {
    var strings = getEnvStrings();
    HEAPU32[penviron_count >> 2] = strings.length;
    var bufSize = 0;
    strings.forEach(function (string) {
      bufSize += string.length + 1;
    });
    HEAPU32[penviron_buf_size >> 2] = bufSize;
    return 0;
  }

  function _proc_exit(code) {
    quit_(code, new ExitStatus(code));
  }

  function exitJS(status, implicit) {
    _proc_exit(status);
  }

  var _exit = exitJS;

  function _fd_close(fd) {
    return 52;
  }

  function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
    return 70;
  }

  var printCharBuffers = [null, [], []];

  function printChar(stream, curr) {
    // eslint-disable-next-line @typescript-eslint/no-shadow
    var buffer = printCharBuffers[stream];
    if (curr === 0 || curr === 10) {
      (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
      buffer.length = 0;
    } else {
      buffer.push(curr);
    }
  }

  function _fd_write(fd, iov, iovcnt, pnum) {
    var num = 0;
    for (var i = 0; i < iovcnt; i++) {
      var ptr = HEAPU32[iov >> 2];
      var len = HEAPU32[(iov + 4) >> 2];
      iov += 8;
      for (var j = 0; j < len; j++) {
        printChar(fd, HEAPU8[ptr + j]);
      }
      num += len;
    }
    HEAPU32[pnum >> 2] = num;
    return 0;
  }

  function getRandomDevice() {
    if (
      typeof crypto == 'object' &&
      typeof crypto['getRandomValues'] == 'function'
    ) {
      var randomBuffer = new Uint8Array(1);
      return () => {
        crypto.getRandomValues(randomBuffer);
        return randomBuffer[0];
      };
    } else return () => abort('randomDevice');
  }

  // eslint-disable-next-line @typescript-eslint/no-shadow
  function _getentropy(buffer, size) {
    if (!_getentropy.randomDevice) {
      _getentropy.randomDevice = getRandomDevice();
    }
    for (var i = 0; i < size; i++) {
      HEAP8[(buffer + i) >> 0] = _getentropy.randomDevice();
    }
    return 0;
  }

  var asmLibraryArg = {
    _dlinit: __dlinit,
    _dlopen_js: __dlopen_js,
    _dlsym_js: __dlsym_js,
    _emscripten_get_now_is_monotonic: __emscripten_get_now_is_monotonic,
    _mmap_js: __mmap_js,
    _munmap_js: __munmap_js,
    abort: _abort,
    emscripten_date_now: _emscripten_date_now,
    emscripten_get_heap_max: _emscripten_get_heap_max,
    emscripten_get_now: _emscripten_get_now,
    emscripten_memcpy_big: _emscripten_memcpy_big,
    emscripten_resize_heap: _emscripten_resize_heap,
    environ_get: _environ_get,
    environ_sizes_get: _environ_sizes_get,
    exit: _exit,
    fd_close: _fd_close,
    fd_seek: _fd_seek,
    fd_write: _fd_write,
    getentropy: _getentropy,
  };
  createWasm();
  (Module['___wasm_call_ctors'] = function () {
    return (Module['___wasm_call_ctors'] =
      Module['asm']['__wasm_call_ctors']).apply(null, arguments);
  });
  (Module['_getModelBufferMemoryOffset'] =
    function () {
      return (Module[
        '_getModelBufferMemoryOffset'
      ] =
        Module['asm']['getModelBufferMemoryOffset']).apply(null, arguments);
    });
  (Module['_getInputMemoryOffset'] = function () {
    return (Module['_getInputMemoryOffset'] =
      Module['asm']['getInputMemoryOffset']).apply(null, arguments);
  });
  (Module['_getInputHeight'] = function () {
    return (Module['_getInputHeight'] =
      Module['asm']['getInputHeight']).apply(null, arguments);
  });
  (Module['_getInputWidth'] = function () {
    return (Module['_getInputWidth'] =
      Module['asm']['getInputWidth']).apply(null, arguments);
  });
  (Module['_getInputChannelCount'] = function () {
    return (Module['_getInputChannelCount'] =
      Module['asm']['getInputChannelCount']).apply(null, arguments);
  });
  (Module['_getOutputMemoryOffset'] = function () {
    return (Module['_getOutputMemoryOffset'] =
      Module['asm']['getOutputMemoryOffset']).apply(null, arguments);
  });
  (Module['_getOutputHeight'] = function () {
    return (Module['_getOutputHeight'] =
      Module['asm']['getOutputHeight']).apply(null, arguments);
  });
  (Module['_getOutputWidth'] = function () {
    return (Module['_getOutputWidth'] =
      Module['asm']['getOutputWidth']).apply(null, arguments);
  });
  (Module['_getOutputChannelCount'] = function () {
    return (Module['_getOutputChannelCount'] =
      Module['asm']['getOutputChannelCount']).apply(null, arguments);
  });
  (Module['_loadModel'] = function () {
    return (Module['_loadModel'] =
      Module['asm']['loadModel']).apply(null, arguments);
  });
  (Module['_runInference'] = function () {
    return (Module['_runInference'] =
      Module['asm']['runInference']).apply(null, arguments);
  });
  (Module['_malloc'] = function () {
    return (Module['_malloc'] = Module['asm']['malloc']).apply(
      null,
      arguments,
    );
  });
  (Module['___errno_location'] = function () {
    return (Module['___errno_location'] =
      Module['asm']['__errno_location']).apply(null, arguments);
  });
  (Module['___dl_seterr'] = function () {
    return (Module['___dl_seterr'] =
      Module['asm']['__dl_seterr']).apply(null, arguments);
  });
  (Module['stackSave'] = function () {
    return (Module['stackSave'] = Module['asm']['stackSave']).apply(
      null,
      arguments,
    );
  });
  (Module['stackRestore'] = function () {
    return (Module['stackRestore'] =
      Module['asm']['stackRestore']).apply(null, arguments);
  });
  (Module['stackAlloc'] = function () {
    return (Module['stackAlloc'] =
      Module['asm']['stackAlloc']).apply(null, arguments);
  });
  (Module['dynCall_jjj'] = function () {
    return (Module['dynCall_jjj'] =
      Module['asm']['dynCall_jjj']).apply(null, arguments);
  });
  (Module['dynCall_jiii'] = function () {
    return (Module['dynCall_jiii'] =
      Module['asm']['dynCall_jiii']).apply(null, arguments);
  });
  (Module['dynCall_iiiijj'] = function () {
    return (Module['dynCall_iiiijj'] =
      Module['asm']['dynCall_iiiijj']).apply(null, arguments);
  });
  (Module['dynCall_viijj'] = function () {
    return (Module['dynCall_viijj'] =
      Module['asm']['dynCall_viijj']).apply(null, arguments);
  });
  (Module['dynCall_viiijjj'] = function () {
    return (Module['dynCall_viiijjj'] =
      Module['asm']['dynCall_viiijjj']).apply(null, arguments);
  });
  (Module['dynCall_iijjiiii'] = function () {
    return (Module['dynCall_iijjiiii'] =
      Module['asm']['dynCall_iijjiiii']).apply(null, arguments);
  });
  (Module['dynCall_jiji'] = function () {
    return (Module['dynCall_jiji'] =
      Module['asm']['dynCall_jiji']).apply(null, arguments);
  });
  var calledRun;
  dependenciesFulfilled = function runCaller() {
    if (!calledRun) run();
    if (!calledRun) dependenciesFulfilled = runCaller;
  };

  function run(args) {
    if (runDependencies > 0) {
      return;
    }
    preRun();
    if (runDependencies > 0) {
      return;
    }

    function doRun() {
      if (calledRun) return;
      calledRun = true;
      Module['calledRun'] = true;
      if (ABORT) return;
      initRuntime();
      readyPromiseResolve(Module);
      if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();
      postRun();
    }

    if (Module['setStatus']) {
      Module['setStatus']('Running...');
      setTimeout(function () {
        setTimeout(function () {
          Module['setStatus']('');
        }, 1);
        doRun();
      }, 1);
    } else {
      doRun();
    }
  }

  if (Module['preInit']) {
    if (typeof Module['preInit'] == 'function')
      Module['preInit'] = [Module['preInit']];
    while (Module['preInit'].length > 0) {
      Module['preInit'].pop()();
    }
  }
  run();

  return __Module.ready;
};

const version = "0.2.0";
const packageName = "@stream-io/video-filters-web";

// @ts-expect-error - module is not declared
// This is a WebAssembly module compiled from the TensorFlow Lite C++ library.
const createTFLite = createTFLiteSIMDModule;
const loadTFLite = async (options = {}) => {
    const { basePath = `https://unpkg.com/${packageName}@${version}/tf`, tfFilePath = `${basePath}/tflite/tflite-simd.wasm`, modelFilePath = `${basePath}/models/segm_full_v679.tflite`, } = options;
    const [tfLite, model] = await Promise.all([
        createTFLite({ locateFile: () => tfFilePath }),
        fetchModel(modelFilePath),
    ]);
    const modelBufferOffset = tfLite._getModelBufferMemoryOffset();
    tfLite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
    tfLite._loadModel(model.byteLength);
    return tfLite;
};
let lastModelFilePath = '';
let modelFileCache;
const fetchModel = async (modelFilePath) => {
    const model = modelFilePath === lastModelFilePath && modelFileCache
        ? modelFileCache
        : await fetch(modelFilePath).then((r) => r.arrayBuffer());
    // Cache the model file for future use.
    modelFileCache = model;
    lastModelFilePath = modelFilePath;
    return model;
};

export { SegmentationLevel, createRenderer, isPlatformSupported, loadTFLite };
//# sourceMappingURL=index.es.js.map
