@pexip/media-processor 20.3.5 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/api-docs/README.mdx +33 -6
  3. package/api-docs/functions/createBenchmark.mdx +17 -5
  4. package/api-docs/functions/createWindowedStats.mdx +23 -0
  5. package/api-docs/functions/iou.mdx +19 -0
  6. package/api-docs/functions/labelComponents.mdx +20 -0
  7. package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
  8. package/api-docs/functions/playVideo.mdx +16 -0
  9. package/api-docs/functions/scoreComponent.mdx +22 -0
  10. package/api-docs/functions/setVideoElementSrc.mdx +14 -0
  11. package/api-docs/interfaces/Benchmark.mdx +12 -0
  12. package/api-docs/interfaces/RendererOptions.mdx +26 -9
  13. package/api-docs/interfaces/SegmentationParams.mdx +29 -12
  14. package/api-docs/interfaces/SelectionOptions.mdx +15 -0
  15. package/api-docs/interfaces/Stats.mdx +11 -0
  16. package/api-docs/interfaces/Weights.mdx +12 -0
  17. package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
  18. package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
  19. package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
  20. package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
  21. package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
  22. package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
  23. package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
  24. package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
  25. package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
  26. package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
  27. package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
  28. package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
  29. package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
  30. package/api-docs/variables/MORPH_PASS.mdx +3 -0
  31. package/api-docs/variables/PERSON_CENTER.mdx +3 -0
  32. package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
  33. package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
  34. package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
  35. package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
  36. package/api-docs/variables/getImageSize.mdx +13 -0
  37. package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
  38. package/dist/common/backends/webgl/binaryMask.js +95 -0
  39. package/dist/common/backends/webgl/blender.d.ts +2 -1
  40. package/dist/common/backends/webgl/blender.js +18 -15
  41. package/dist/common/backends/webgl/blur.d.ts +1 -1
  42. package/dist/common/backends/webgl/blur.js +23 -16
  43. package/dist/common/backends/webgl/canvas.js +9 -4
  44. package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
  45. package/dist/common/backends/webgl/diffAbsMask.js +103 -0
  46. package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
  47. package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
  48. package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
  49. package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
  50. package/dist/common/backends/webgl/morphErode.d.ts +13 -0
  51. package/dist/common/backends/webgl/morphErode.js +121 -0
  52. package/dist/common/backends/webgl/renderer.js +194 -34
  53. package/dist/common/backends/webgl/replace.d.ts +17 -0
  54. package/dist/common/backends/webgl/replace.js +126 -0
  55. package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
  56. package/dist/common/backends/webgl/selectComponent.js +102 -0
  57. package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
  58. package/dist/common/backends/webgl/smoothingMask.js +22 -21
  59. package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
  60. package/dist/common/backends/webgl/tentBlur.js +118 -0
  61. package/dist/common/backends/webgl/texture.d.ts +1 -1
  62. package/dist/common/backends/webgl/texture.js +19 -17
  63. package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
  64. package/dist/common/backends/webgl/textureToTexture.js +19 -16
  65. package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
  66. package/dist/common/backends/webgl/webglUtils.js +64 -6
  67. package/dist/common/backends/webgpu/blender.d.ts +6 -1
  68. package/dist/common/backends/webgpu/blender.js +96 -51
  69. package/dist/common/backends/webgpu/constants.js +1 -1
  70. package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
  71. package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
  72. package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
  73. package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
  74. package/dist/common/backends/webgpu/renderer.js +211 -39
  75. package/dist/common/backends/webgpu/replace.d.ts +11 -0
  76. package/dist/common/backends/webgpu/replace.js +175 -0
  77. package/dist/common/backends/webgpu/resize.d.ts +4 -0
  78. package/dist/common/backends/webgpu/resize.js +76 -0
  79. package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
  80. package/dist/common/backends/webgpu/smoothingMask.js +74 -40
  81. package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
  82. package/dist/common/backends/webgpu/tentBlur.js +131 -0
  83. package/dist/common/backends/webgpu/texture.d.ts +2 -2
  84. package/dist/common/backends/webgpu/texture.js +24 -19
  85. package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
  86. package/dist/common/backends/webgpu/textureToTexture.js +28 -15
  87. package/dist/common/backends/webgpu/types.d.ts +2 -0
  88. package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
  89. package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
  90. package/dist/common/constants.d.ts +19 -5
  91. package/dist/common/constants.js +19 -5
  92. package/dist/common/index.d.ts +1 -0
  93. package/dist/common/index.js +1 -0
  94. package/dist/common/tsconfig.tsbuildinfo +1 -1
  95. package/dist/common/types/render.d.ts +142 -13
  96. package/dist/common/types/utils.d.ts +21 -0
  97. package/dist/common/utils.d.ts +16 -0
  98. package/dist/common/utils.js +152 -0
  99. package/dist/main/benchUtils.d.ts +33 -1
  100. package/dist/main/benchUtils.js +145 -11
  101. package/dist/main/tsconfig.tsbuildinfo +1 -1
  102. package/dist/main/video/canvasTransform.d.ts +1 -1
  103. package/dist/main/video/canvasTransform.js +135 -47
  104. package/dist/main/video/index.d.ts +1 -1
  105. package/dist/main/video/index.js +1 -1
  106. package/dist/workers/mediaWorker.js +35 -162
  107. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  108. package/package.json +8 -7
  109. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
  110. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
  111. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
  112. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
  113. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
  114. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
  115. package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
  116. package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
  117. package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
  118. package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
@@ -0,0 +1,108 @@
1
+ import { assert } from '@pexip/utils';
2
+ import { RENDERING_EVENTS } from '../../constants';
3
+ import { m3 } from '../../m3';
4
+ import { createGLProgram, createMaskTexture, createFrameBuffer, } from './webglUtils';
5
+ import vertexSrc from './shaders/matrix.vert';
6
+ import fragmentSrc from './shaders/jointBilateralFilter.frag';
7
+ /**
8
+ * Joint Bilateral Filter
9
+ */
10
+ export const createJointBilateralFilterRenderer = (processor, width, height) => {
11
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
12
+ attribs: {
13
+ position: 'a_position',
14
+ texCoord: 'a_texCoord',
15
+ },
16
+ uniforms: {
17
+ frameGuide: 'u_frame_guide',
18
+ frameInput: 'u_frame_input',
19
+ matrix: 'u_matrix',
20
+ texelSize: 'u_texel_size',
21
+ sigmaSpace: 'u_sigma_space',
22
+ sigmaRangeLo: 'u_sigma_range_lo',
23
+ sigmaRangeHi: 'u_sigma_range_hi',
24
+ foregroundThreshold: 'u_foreground_threshold',
25
+ backgroundThreshold: 'u_background_threshold',
26
+ },
27
+ vertexSrc,
28
+ fragmentSrc,
29
+ });
30
+ gl.enable(gl.BLEND);
31
+ const vao = gl.createVertexArray();
32
+ gl.bindVertexArray(vao);
33
+ // Position
34
+ const positionBuffer = gl.createBuffer();
35
+ gl.enableVertexAttribArray(attribLocations.position);
36
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
37
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
38
+ gl.bufferData(gl.ARRAY_BUFFER,
39
+ // prettier-ignore
40
+ new Float32Array([
41
+ 0, 0,
42
+ 0, 1,
43
+ 1, 0,
44
+ 1, 0,
45
+ 0, 1,
46
+ 1, 1,
47
+ ]), gl.STATIC_DRAW);
48
+ // Texture coordinates for the rectangle
49
+ const texCoordBuffer = gl.createBuffer();
50
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
51
+ gl.bufferData(gl.ARRAY_BUFFER,
52
+ // prettier-ignore
53
+ new Float32Array([
54
+ 0, 0,
55
+ 0, 1,
56
+ 1, 0,
57
+ 1, 0,
58
+ 0, 1,
59
+ 1, 1,
60
+ ]), gl.STATIC_DRAW);
61
+ gl.enableVertexAttribArray(attribLocations.texCoord);
62
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
63
+ const outputTexture = createMaskTexture(gl, width, height);
64
+ const frameBuffer = createFrameBuffer(gl, outputTexture);
65
+ const scaleFactor = processor.width / width;
66
+ return {
67
+ render: ({ input, guide }, options) => {
68
+ assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
69
+ assert(options.foregroundThreshold >= options.backgroundThreshold, 'foregroundThreshold must be >= backgroundThreshold');
70
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
71
+ gl.viewport(0, 0, width, height);
72
+ gl.useProgram(program);
73
+ gl.bindVertexArray(vao);
74
+ // Textures
75
+ gl.activeTexture(gl.TEXTURE0);
76
+ gl.uniform1i(uniformLocations.frameInput, 0);
77
+ gl.bindTexture(gl.TEXTURE_2D, input.texture);
78
+ gl.activeTexture(gl.TEXTURE1);
79
+ gl.uniform1i(uniformLocations.frameGuide, 1);
80
+ gl.bindTexture(gl.TEXTURE_2D, guide.texture);
81
+ gl.uniform2f(uniformLocations.texelSize, 1.0 / input.width, 1.0 / input.height);
82
+ gl.uniform1f(uniformLocations.sigmaSpace, options.sigmaSpace / scaleFactor);
83
+ gl.uniform1f(uniformLocations.sigmaRangeLo, options.sigmaRangeLo);
84
+ gl.uniform1f(uniformLocations.sigmaRangeHi, options.sigmaRangeHi);
85
+ gl.uniform1f(uniformLocations.backgroundThreshold, options.backgroundThreshold);
86
+ gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
87
+ // No Flip Y Coordinate for drawing on the frame buffer
88
+ // Project, position and scale to full-size of the canvas
89
+ const matrix = m3.projection(1, 1, 1);
90
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
91
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
92
+ // Cleanup
93
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
94
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
95
+ gl.bindTexture(gl.TEXTURE_2D, null);
96
+ gl.bindVertexArray(null);
97
+ return { texture: outputTexture, width, height };
98
+ },
99
+ release: () => {
100
+ gl.deleteBuffer(positionBuffer);
101
+ gl.deleteBuffer(texCoordBuffer);
102
+ gl.deleteTexture(outputTexture);
103
+ gl.deleteFramebuffer(frameBuffer);
104
+ gl.deleteVertexArray(vao);
105
+ release();
106
+ },
107
+ };
108
+ };
@@ -0,0 +1,13 @@
1
+ import type { RendererOptions } from '../../types/render';
2
+ import type { TextureInfo } from './types';
3
+ /**
4
+ * Create a morphological erosion renderer that performs erosion of a mask with a
5
+ * given radius and number of passes.
6
+ * The erosion is implemented as a separable filter.
7
+ */
8
+ export declare const createMorphErodeRenderer: (processor: OffscreenCanvas, width: number, height: number) => {
9
+ render: ({ mask }: {
10
+ mask: TextureInfo;
11
+ }, options: Pick<RendererOptions, "morphPass" | "morphErodeRadiusPx">) => TextureInfo;
12
+ release: () => void;
13
+ };
@@ -0,0 +1,121 @@
1
+ import { assert } from '@pexip/utils';
2
+ import { m3 } from '../../m3';
3
+ import { RENDERING_EVENTS } from '../../constants';
4
+ import { clamping } from '../../utils';
5
+ import { createGLProgram, createMaskTexture, createFrameBuffer, } from './webglUtils';
6
+ import vertexSrc from './shaders/matrix.vert';
7
+ import fragmentSrc from './shaders/erode.frag';
8
+ const MAX_RADIUS_PX = 64;
9
+ const clampRadiusPx = clamping(0, MAX_RADIUS_PX);
10
+ /**
11
+ * Create a morphological erosion renderer that performs erosion of a mask with a
12
+ * given radius and number of passes.
13
+ * The erosion is implemented as a separable filter.
14
+ */
15
+ export const createMorphErodeRenderer = (processor, width, height) => {
16
+ const { gl, program, attribLocations, uniformLocations, floatSingleChannelSupported, floatFullChannelSupported, release, } = createGLProgram(processor, {
17
+ attribs: {
18
+ position: 'a_position',
19
+ texCoord: 'a_texCoord',
20
+ },
21
+ uniforms: {
22
+ matrix: 'u_matrix',
23
+ mask: 'u_mask',
24
+ texelSize: 'u_texel_size',
25
+ radiusPx: 'u_radius_px',
26
+ direction: 'u_direction',
27
+ },
28
+ vertexSrc,
29
+ fragmentSrc,
30
+ });
31
+ const vao = gl.createVertexArray();
32
+ gl.bindVertexArray(vao);
33
+ // Position
34
+ const positionBuffer = gl.createBuffer();
35
+ gl.enableVertexAttribArray(attribLocations.position);
36
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
37
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
38
+ gl.bufferData(gl.ARRAY_BUFFER,
39
+ // prettier-ignore
40
+ new Float32Array([
41
+ 0, 0,
42
+ 0, 1,
43
+ 1, 0,
44
+ 1, 0,
45
+ 0, 1,
46
+ 1, 1,
47
+ ]), gl.STATIC_DRAW);
48
+ // Texture coordinates for the rectangle
49
+ const texCoordBuffer = gl.createBuffer();
50
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
51
+ gl.bufferData(gl.ARRAY_BUFFER,
52
+ // prettier-ignore
53
+ new Float32Array([
54
+ 0, 0,
55
+ 0, 1,
56
+ 1, 0,
57
+ 1, 0,
58
+ 0, 1,
59
+ 1, 1,
60
+ ]), gl.STATIC_DRAW);
61
+ gl.enableVertexAttribArray(attribLocations.texCoord);
62
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
63
+ const outputTexturePing = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
64
+ const frameBufferPing = createFrameBuffer(gl, outputTexturePing);
65
+ const outputTexturePong = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
66
+ const frameBufferPong = createFrameBuffer(gl, outputTexturePong);
67
+ const scaleFactor = processor.width / width;
68
+ return {
69
+ render: ({ mask }, options) => {
70
+ assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
71
+ gl.clearColor(0, 0, 0, 0);
72
+ gl.clear(gl.COLOR_BUFFER_BIT);
73
+ gl.useProgram(program);
74
+ gl.bindVertexArray(vao);
75
+ gl.uniform2f(uniformLocations.texelSize, 1 / mask.width, 1 / mask.height);
76
+ gl.uniform1i(uniformLocations.radiusPx, clampRadiusPx(Math.floor(options.morphErodeRadiusPx / scaleFactor)));
77
+ const matrix = m3.projection(1, 1, 1);
78
+ // No Flip Y Coordinate for drawing on the frame buffer
79
+ // Project, position and scale to full-size of the canvas
80
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
81
+ let write = frameBufferPing;
82
+ let read = mask.texture;
83
+ // Ping-pong framebuffers for multiple passes
84
+ for (let i = 0; i < options.morphPass; i++) {
85
+ // Separable
86
+ for (let j = 0; j < 2; j++) {
87
+ // Textures
88
+ gl.activeTexture(gl.TEXTURE0);
89
+ gl.uniform1i(uniformLocations.mask, 0);
90
+ gl.bindTexture(gl.TEXTURE_2D, read);
91
+ // Horizontal then Vertical
92
+ gl.uniform2f(uniformLocations.direction, 1.0 - j, 0.0 + j);
93
+ gl.bindFramebuffer(gl.FRAMEBUFFER, write);
94
+ gl.viewport(0, 0, mask.width, mask.height);
95
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
96
+ // Swap
97
+ [read, write] =
98
+ write === frameBufferPing
99
+ ? [outputTexturePing, frameBufferPong]
100
+ : [outputTexturePong, frameBufferPing];
101
+ }
102
+ }
103
+ // Cleanup
104
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
105
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
106
+ gl.bindTexture(gl.TEXTURE_2D, null);
107
+ gl.bindVertexArray(null);
108
+ return { texture: read, width, height };
109
+ },
110
+ release: () => {
111
+ gl.deleteBuffer(positionBuffer);
112
+ gl.deleteBuffer(texCoordBuffer);
113
+ gl.deleteTexture(outputTexturePing);
114
+ gl.deleteTexture(outputTexturePong);
115
+ gl.deleteFramebuffer(frameBufferPing);
116
+ gl.deleteFramebuffer(frameBufferPong);
117
+ gl.deleteVertexArray(vao);
118
+ release();
119
+ },
120
+ };
121
+ };
@@ -1,23 +1,59 @@
1
1
  import { assert } from '@pexip/utils';
2
- import { PROCESSING_WIDTH, PROCESSING_HEIGHT, EDGE_BLUR_AMOUNT, BACKGROUND_BLUR_AMOUNT, RENDERING_EVENTS, } from '../../constants';
3
- import { createTextureRenderer } from './texture';
4
- import { createBlurRenderer } from './blur';
2
+ import { BACKGROUND_BLUR_AMOUNT, PROCESSING_HEIGHT, PROCESSING_WIDTH, RENDERING_EVENTS, STRONG_EDGE_BLUR_AMOUNT, } from '../../constants';
3
+ import { createLazyProps, handleWebGLContextLoss } from '../../utils';
4
+ import { createBinaryMaskRenderer } from './binaryMask';
5
5
  import { createBlendRenderer } from './blender';
6
+ import { createBlurRenderer } from './blur';
6
7
  import { createCanvasRenderer } from './canvas';
7
- import { createTextureToTextureRenderer } from './textureToTexture';
8
+ import { createComponentSelector } from './selectComponent';
9
+ import { createGeodesicReconstructionRenderer } from './geodesicReconstruction';
10
+ import { createJointBilateralFilterRenderer } from './jointBilateralFilter';
11
+ import { createMorphErodeRenderer } from './morphErode';
12
+ import { createReplaceRenderer } from './replace';
8
13
  import { createSmoothingMaskRenderer } from './smoothingMask';
9
- import { createLazyProps, handleWebGLContextLoss } from '../../utils';
14
+ import { createTentBlurRenderer } from './tentBlur';
15
+ import { createTextureRenderer } from './texture';
16
+ import { createTextureToTextureRenderer } from './textureToTexture';
10
17
  export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas(PROCESSING_WIDTH, PROCESSING_HEIGHT)) => {
18
+ const halfWidth = Math.trunc(canvas.width / 2);
19
+ const halfHeight = Math.trunc(canvas.height / 2);
20
+ const quarterWidth = Math.trunc(canvas.width / 4);
21
+ const quarterHeight = Math.trunc(canvas.height / 4);
22
+ // Width and Height used for the Center Component selection
23
+ const cclWidth = 32;
24
+ const cclHeight = 18;
11
25
  const rendererCreator = {
12
- videoFrameRenderer: () => createTextureRenderer(canvas),
13
- maskRenderer: () => createTextureRenderer(canvas),
14
- canvasRenderer: () => createCanvasRenderer(canvas),
15
- backgroundImageRenderer: () => createTextureRenderer(canvas),
26
+ backgroundImageRenderer: () => createTextureRenderer(canvas, false),
16
27
  blendRenderer: () => createBlendRenderer(canvas),
17
28
  blurRenderer: () => createBlurRenderer(canvas, BACKGROUND_BLUR_AMOUNT),
18
- maskBlurRenderer: () => createBlurRenderer(canvas, EDGE_BLUR_AMOUNT),
19
- textureRenderer: () => createTextureToTextureRenderer(canvas),
20
- smoothingMaskRenderer: () => createSmoothingMaskRenderer(canvas),
29
+ canvasRenderer: () => createCanvasRenderer(canvas),
30
+ frameDownsampler2: () => createTextureToTextureRenderer(canvas, false, true, halfWidth, halfHeight),
31
+ frameDownsampler4: () => createTextureToTextureRenderer(canvas, false, true, quarterWidth, quarterHeight),
32
+ geodesicReconstructionRenderer2: () => createGeodesicReconstructionRenderer(canvas, halfWidth, halfHeight),
33
+ geodesicReconstructionRenderer4: () => createGeodesicReconstructionRenderer(canvas, quarterWidth, quarterHeight),
34
+ jointBilateralFilterRenderer2: () => createJointBilateralFilterRenderer(canvas, halfWidth, halfHeight),
35
+ jointBilateralFilterRenderer4: () => createJointBilateralFilterRenderer(canvas, quarterWidth, quarterHeight),
36
+ jointBilateralFilterRenderer: () => createJointBilateralFilterRenderer(canvas, canvas.width, canvas.height),
37
+ lightWrapBlurRenderer: () => createBlurRenderer(canvas, BACKGROUND_BLUR_AMOUNT),
38
+ maskBlurRenderer: () => createTentBlurRenderer(canvas, canvas.width, canvas.height),
39
+ strongMaskBlurRenderer: () => createBlurRenderer(canvas, STRONG_EDGE_BLUR_AMOUNT),
40
+ maskDownsampler2: () => createTextureToTextureRenderer(canvas, true, false, halfWidth, halfHeight),
41
+ maskDownsampler4: () => createTextureToTextureRenderer(canvas, true, false, quarterWidth, quarterHeight),
42
+ maskRenderer: () => createTextureRenderer(canvas, true),
43
+ maskUpsampler2: () => createTextureToTextureRenderer(canvas, true, false, halfWidth, halfHeight),
44
+ maskUpsampler4: () => createTextureToTextureRenderer(canvas, true, false, quarterWidth, quarterHeight),
45
+ maskUpsampler: () => createTextureToTextureRenderer(canvas, true, false, canvas.width, canvas.height),
46
+ morphErodeRenderer: () => createMorphErodeRenderer(canvas, halfWidth, halfHeight),
47
+ morphErodeRenderer4: () => createMorphErodeRenderer(canvas, quarterWidth, quarterHeight),
48
+ prevMaskRenderer2: () => createTextureToTextureRenderer(canvas, true, false, halfWidth, halfHeight),
49
+ prevMaskRenderer4: () => createTextureToTextureRenderer(canvas, true, false, quarterWidth, quarterHeight),
50
+ replaceRenderer: () => createReplaceRenderer(canvas),
51
+ selectCenterComponent: () => createComponentSelector(canvas, cclWidth, cclHeight),
52
+ smoothingMaskRenderer4: () => createSmoothingMaskRenderer(canvas, quarterWidth, quarterHeight),
53
+ smoothingMaskRenderer2: () => createSmoothingMaskRenderer(canvas, halfWidth, halfHeight),
54
+ binaryMaskRenderer2: () => createBinaryMaskRenderer(canvas, halfWidth, halfHeight),
55
+ binaryMaskRenderer4: () => createBinaryMaskRenderer(canvas, quarterWidth, quarterHeight),
56
+ videoFrameRenderer: () => createTextureRenderer(canvas, false),
21
57
  release: (states) => () => {
22
58
  for (const key in states) {
23
59
  if (key !== 'release') {
@@ -35,6 +71,15 @@ export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas
35
71
  get videoFrameRenderer() {
36
72
  return lazyProps.videoFrameRenderer;
37
73
  },
74
+ get jointBilateralFilterRenderer() {
75
+ return lazyProps.jointBilateralFilterRenderer;
76
+ },
77
+ get jointBilateralFilterRenderer2() {
78
+ return lazyProps.jointBilateralFilterRenderer2;
79
+ },
80
+ get jointBilateralFilterRenderer4() {
81
+ return lazyProps.jointBilateralFilterRenderer4;
82
+ },
38
83
  get maskRenderer() {
39
84
  return lazyProps.maskRenderer;
40
85
  },
@@ -47,18 +92,74 @@ export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas
47
92
  get blendRenderer() {
48
93
  return lazyProps.blendRenderer;
49
94
  },
95
+ get replaceRenderer() {
96
+ return lazyProps.replaceRenderer;
97
+ },
50
98
  get blurRenderer() {
51
99
  return lazyProps.blurRenderer;
52
100
  },
53
- // TODO: Impl edge blur
101
+ get lightWrapBlurRenderer() {
102
+ return lazyProps.lightWrapBlurRenderer;
103
+ },
104
+ get prevMaskRenderer2() {
105
+ return lazyProps.prevMaskRenderer2;
106
+ },
107
+ get prevMaskRenderer4() {
108
+ return lazyProps.prevMaskRenderer4;
109
+ },
110
+ get selectCenterComponent() {
111
+ return lazyProps.selectCenterComponent;
112
+ },
113
+ get smoothingMaskRenderer2() {
114
+ return lazyProps.smoothingMaskRenderer2;
115
+ },
116
+ get smoothingMaskRenderer4() {
117
+ return lazyProps.smoothingMaskRenderer4;
118
+ },
119
+ get binaryMaskRenderer2() {
120
+ return lazyProps.binaryMaskRenderer2;
121
+ },
122
+ get binaryMaskRenderer4() {
123
+ return lazyProps.binaryMaskRenderer4;
124
+ },
125
+ get morphErodeRenderer() {
126
+ return lazyProps.morphErodeRenderer;
127
+ },
128
+ get morphErodeRenderer4() {
129
+ return lazyProps.morphErodeRenderer4;
130
+ },
54
131
  get maskBlurRenderer() {
55
132
  return lazyProps.maskBlurRenderer;
56
133
  },
57
- get textureRenderer() {
58
- return lazyProps.textureRenderer;
134
+ get strongMaskBlurRenderer() {
135
+ return lazyProps.strongMaskBlurRenderer;
136
+ },
137
+ get maskDownsampler2() {
138
+ return lazyProps.maskDownsampler2;
139
+ },
140
+ get maskDownsampler4() {
141
+ return lazyProps.maskDownsampler4;
142
+ },
143
+ get maskUpsampler() {
144
+ return lazyProps.maskUpsampler;
145
+ },
146
+ get maskUpsampler2() {
147
+ return lazyProps.maskUpsampler2;
148
+ },
149
+ get maskUpsampler4() {
150
+ return lazyProps.maskUpsampler4;
151
+ },
152
+ get frameDownsampler2() {
153
+ return lazyProps.frameDownsampler2;
154
+ },
155
+ get frameDownsampler4() {
156
+ return lazyProps.frameDownsampler4;
59
157
  },
60
- get smoothingMaskRenderer() {
61
- return lazyProps.smoothingMaskRenderer;
158
+ get geodesicReconstructionRenderer2() {
159
+ return lazyProps.geodesicReconstructionRenderer2;
160
+ },
161
+ get geodesicReconstructionRenderer4() {
162
+ return lazyProps.geodesicReconstructionRenderer4;
62
163
  },
63
164
  };
64
165
  const init = () => {
@@ -75,43 +176,102 @@ export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas
75
176
  };
76
177
  const render = (mask, frame, options) => {
77
178
  assert(!props.gl?.isContextLost(), RENDERING_EVENTS.ContextLost);
179
+ // 1. Upload current frame and raw mask
180
+ const frameTexture = props.videoFrameRenderer.render(frame);
78
181
  const maskTexture = props.maskRenderer.render({
79
182
  frame: mask,
80
183
  width: mask.width,
81
184
  height: mask.height,
82
185
  timestamp: frame.timestamp,
83
186
  });
84
- const smoothedMask = props.smoothingMaskRenderer.render({
85
- mask: maskTexture,
86
- prevMask: props.prevMask,
87
- }, options);
88
- const frameTexture = props.videoFrameRenderer.render(frame);
89
- let background = frameTexture;
187
+ // 2. Joint Bilateral Filter
188
+ let smoothedMask;
189
+ let compositeMaskInput;
190
+ switch (options.downSampleFactor) {
191
+ case 4: {
192
+ const maskLowRes = props.maskDownsampler4.render(maskTexture);
193
+ const frameLowRes = props.frameDownsampler4.render(frameTexture);
194
+ const filteredMaskLowRes = props.jointBilateralFilterRenderer4.render({ input: maskLowRes, guide: frameLowRes }, options);
195
+ smoothedMask = props.smoothingMaskRenderer4.render({
196
+ mask: filteredMaskLowRes,
197
+ prevMask: props.prevMask4 ?? filteredMaskLowRes,
198
+ }, options);
199
+ if (options.excludeBystanders) {
200
+ const binary = props.binaryMaskRenderer4.render(smoothedMask, options.foregroundThreshold);
201
+ const eroded = props.morphErodeRenderer4.render({ mask: binary }, options);
202
+ const selected = props.selectCenterComponent.render(eroded, options);
203
+ const filted = props.maskUpsampler4.render(selected);
204
+ smoothedMask = props.geodesicReconstructionRenderer4.render({ mask: filted, maskRef: smoothedMask }, options);
205
+ }
206
+ // Upsample to original size
207
+ const result = props.maskUpsampler.render(smoothedMask);
208
+ compositeMaskInput = props.strongMaskBlurRenderer.render(result, STRONG_EDGE_BLUR_AMOUNT);
209
+ break;
210
+ }
211
+ default: {
212
+ const maskLowRes = props.maskDownsampler2.render(maskTexture);
213
+ const frameLowRes = props.frameDownsampler2.render(frameTexture);
214
+ const filteredMaskLowRes = props.jointBilateralFilterRenderer2.render({ input: maskLowRes, guide: frameLowRes }, options);
215
+ smoothedMask = props.smoothingMaskRenderer2.render({
216
+ mask: filteredMaskLowRes,
217
+ prevMask: props.prevMask2 ?? filteredMaskLowRes,
218
+ }, options);
219
+ if (options.excludeBystanders) {
220
+ const binary = props.binaryMaskRenderer2.render(smoothedMask, options.foregroundThreshold);
221
+ const eroded = props.morphErodeRenderer.render({ mask: binary }, options);
222
+ const selected = props.selectCenterComponent.render(eroded, options);
223
+ const filted = props.maskUpsampler2.render(selected);
224
+ smoothedMask = props.geodesicReconstructionRenderer2.render({ mask: filted, maskRef: smoothedMask }, options);
225
+ }
226
+ // Upsample to original size
227
+ const result = props.maskUpsampler.render(smoothedMask);
228
+ compositeMaskInput = props.maskBlurRenderer.render(result, options.edgeBlurAmount);
229
+ break;
230
+ }
231
+ }
232
+ // 5. Composite final output
233
+ let outputFrameTexture = frameTexture;
90
234
  if (options.effects === 'blur' && options.backgroundBlurAmount) {
91
- background = props.blurRenderer.render(frameTexture, options.backgroundBlurAmount);
235
+ const background = props.blurRenderer.render(frameTexture, options.backgroundBlurAmount);
236
+ outputFrameTexture = props.blendRenderer.render({
237
+ mask: compositeMaskInput,
238
+ foreground: frameTexture,
239
+ background,
240
+ }, options);
92
241
  }
93
242
  else if (options?.effects === 'overlay' && options.backgroundImage) {
94
- background = props.backgroundImageRenderer.render({
243
+ const background = props.backgroundImageRenderer.render({
95
244
  frame: options.backgroundImage,
96
245
  timestamp: performance.now(),
97
246
  width: options.backgroundImage.width,
98
247
  height: options.backgroundImage.height,
99
248
  });
249
+ outputFrameTexture = props.replaceRenderer.render({
250
+ mask: compositeMaskInput,
251
+ foreground: frameTexture,
252
+ background,
253
+ blurredBackground: props.lightWrapBlurRenderer.render(background, options.lightWrapBlurAmount),
254
+ timestamp: frame.timestamp,
255
+ }, options);
256
+ }
257
+ // 6. Output to present
258
+ props.canvasRenderer.render(outputFrameTexture);
259
+ // 7. Cloned the current smoothedMask & frame so it can be fed back to the next round
260
+ switch (options.downSampleFactor) {
261
+ case 4:
262
+ props.prevMask4 = props.prevMaskRenderer4.render(smoothedMask);
263
+ break;
264
+ default:
265
+ props.prevMask2 = props.prevMaskRenderer2.render(smoothedMask);
266
+ break;
100
267
  }
101
- const blendedFrameTexture = props.blendRenderer.render({
102
- mask: smoothedMask,
103
- foreground: frameTexture,
104
- background,
105
- });
106
- props.canvasRenderer.render(blendedFrameTexture);
107
- // Cloned the current smoothedMask so it can be fed back to the next round
108
- props.prevMask = props.textureRenderer.render(smoothedMask);
109
268
  };
110
269
  const cleanup = () => {
111
270
  props.canvasContextEventsCleanup();
112
271
  lazyProps.release();
113
272
  lazyProps = createLazyProps(rendererCreator);
114
- props.prevMask = undefined;
273
+ props.prevMask2 = undefined;
274
+ props.prevMask4 = undefined;
115
275
  };
116
276
  const release = () => {
117
277
  cleanup();
@@ -0,0 +1,17 @@
1
+ import type { TextureInfo } from './types';
2
+ import type { RendererOptions } from '../../types/render';
3
+ /**
4
+ * Create a background replacement renderer that composites
5
+ * the foreground and background as well as adding light wrap
6
+ * effect based on the mask and blurred background.
7
+ */
8
+ export declare const createReplaceRenderer: (processor: OffscreenCanvas) => {
9
+ render: ({ mask, foreground, background, blurredBackground, timestamp, }: {
10
+ mask: TextureInfo;
11
+ foreground: TextureInfo;
12
+ background: TextureInfo;
13
+ blurredBackground: TextureInfo;
14
+ timestamp: number;
15
+ }, options: RendererOptions) => TextureInfo;
16
+ release: () => void;
17
+ };
@@ -0,0 +1,126 @@
1
+ import { assert } from '@pexip/utils';
2
+ import { RENDERING_EVENTS } from '../../constants';
3
+ import { m3 } from '../../m3';
4
+ import { createGLProgram, createRGBAFrameTexture, createFrameBuffer, } from './webglUtils';
5
+ import vertexSrc from './shaders/matrix.vert';
6
+ import fragmentSrc from './shaders/replace.frag';
7
+ /**
8
+ * Create a background replacement renderer that composites
9
+ * the foreground and background as well as adding light wrap
10
+ * effect based on the mask and blurred background.
11
+ */
12
+ export const createReplaceRenderer = (processor) => {
13
+ const { gl, program, attribLocations, floatFullChannelSupported, uniformLocations, release, } = createGLProgram(processor, {
14
+ attribs: {
15
+ position: 'a_position',
16
+ texCoord: 'a_texCoord',
17
+ },
18
+ uniforms: {
19
+ matrix: 'u_matrix',
20
+ frameBackground: 'u_frame_background',
21
+ frameForeground: 'u_frame_foreground',
22
+ blurBackground: 'u_blur_background',
23
+ mask: 'u_mask',
24
+ confidenceThresholds: 'u_confidence_thresholds',
25
+ lightWrap: 'u_light_wrap',
26
+ lightWrapTightness: 'u_light_wrap_tightness',
27
+ lightWrapEdgeBand: 'u_edge_band',
28
+ time: 'u_time',
29
+ parity: 'u_parity',
30
+ frameSize: 'u_frame_size',
31
+ },
32
+ vertexSrc,
33
+ fragmentSrc,
34
+ });
35
+ gl.enable(gl.BLEND);
36
+ const vao = gl.createVertexArray();
37
+ gl.bindVertexArray(vao);
38
+ // Position
39
+ const positionBuffer = gl.createBuffer();
40
+ gl.enableVertexAttribArray(attribLocations.position);
41
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
42
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
43
+ gl.bufferData(gl.ARRAY_BUFFER,
44
+ // prettier-ignore
45
+ new Float32Array([
46
+ 0, 0,
47
+ 0, 1,
48
+ 1, 0,
49
+ 1, 0,
50
+ 0, 1,
51
+ 1, 1,
52
+ ]), gl.STATIC_DRAW);
53
+ // Texture coordinates for the rectangle
54
+ const texCoordBuffer = gl.createBuffer();
55
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
56
+ gl.bufferData(gl.ARRAY_BUFFER,
57
+ // prettier-ignore
58
+ new Float32Array([
59
+ 0, 0,
60
+ 0, 1,
61
+ 1, 0,
62
+ 1, 0,
63
+ 0, 1,
64
+ 1, 1,
65
+ ]), gl.STATIC_DRAW);
66
+ gl.enableVertexAttribArray(attribLocations.texCoord);
67
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
68
+ const outputTexture = createRGBAFrameTexture(gl, processor.width, processor.height, true, floatFullChannelSupported);
69
+ const frameBuffer = createFrameBuffer(gl, outputTexture);
70
+ let parity = -1;
71
+ return {
72
+ render: ({ mask, foreground, background, blurredBackground, timestamp, }, options) => {
73
+ assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
74
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
75
+ gl.viewport(0, 0, processor.width, processor.height);
76
+ gl.useProgram(program);
77
+ gl.bindVertexArray(vao);
78
+ // Textures
79
+ gl.activeTexture(gl.TEXTURE0);
80
+ gl.uniform1i(uniformLocations.frameForeground, 0);
81
+ gl.bindTexture(gl.TEXTURE_2D, foreground.texture);
82
+ gl.activeTexture(gl.TEXTURE1);
83
+ gl.uniform1i(uniformLocations.frameBackground, 1);
84
+ gl.bindTexture(gl.TEXTURE_2D, background.texture);
85
+ gl.activeTexture(gl.TEXTURE2);
86
+ gl.uniform1i(uniformLocations.mask, 2);
87
+ gl.bindTexture(gl.TEXTURE_2D, mask.texture);
88
+ gl.activeTexture(gl.TEXTURE3);
89
+ gl.uniform1i(uniformLocations.blurBackground, 3);
90
+ gl.bindTexture(gl.TEXTURE_2D, blurredBackground.texture);
91
+ gl.uniform2f(uniformLocations.confidenceThresholds, options.backgroundThreshold, options.foregroundThreshold);
92
+ gl.uniform1f(uniformLocations.lightWrap, options.lightWrapIntensity);
93
+ gl.uniform1f(uniformLocations.lightWrapTightness, options.lightWrapTightness);
94
+ gl.uniform1f(uniformLocations.lightWrapEdgeBand, options.lightWrapEdgeBand);
95
+ parity = -parity;
96
+ gl.uniform1f(uniformLocations.parity, parity);
97
+ gl.uniform1f(uniformLocations.time,
98
+ // Use seconds and wrap to a modest range to avoid mediump precision loss
99
+ (timestamp * 1e-6) % 1024.0);
100
+ gl.uniform2f(uniformLocations.frameSize, processor.width, processor.height);
101
+ // No Flip Y Coordinate for drawing on the frame buffer
102
+ // Project, position and scale to full-size of the canvas
103
+ const matrix = m3.projection(1, 1, 1);
104
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
105
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
106
+ // Cleanup
107
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
108
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
109
+ gl.bindTexture(gl.TEXTURE_2D, null);
110
+ gl.bindVertexArray(null);
111
+ return {
112
+ texture: outputTexture,
113
+ width: processor.width,
114
+ height: processor.height,
115
+ };
116
+ },
117
+ release: () => {
118
+ gl.deleteBuffer(positionBuffer);
119
+ gl.deleteBuffer(texCoordBuffer);
120
+ gl.deleteTexture(outputTexture);
121
+ gl.deleteFramebuffer(frameBuffer);
122
+ gl.deleteVertexArray(vao);
123
+ release();
124
+ },
125
+ };
126
+ };