@pexip/media-processor 17.3.0 → 18.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 (84) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/common/backends/webgl/blender.d.ts +11 -0
  3. package/dist/common/backends/webgl/blender.js +104 -0
  4. package/dist/common/backends/webgl/blur.d.ts +5 -0
  5. package/dist/common/backends/webgl/blur.js +153 -0
  6. package/dist/common/backends/webgl/canvas.d.ts +5 -0
  7. package/dist/common/backends/webgl/canvas.js +77 -0
  8. package/dist/common/backends/webgl/renderer.d.ts +8 -0
  9. package/dist/common/backends/webgl/renderer.js +126 -0
  10. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -0
  11. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +63 -0
  12. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +1 -0
  13. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +45 -0
  14. package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +1 -0
  15. package/dist/common/backends/webgl/shaders/textureFragmentShader.js +16 -0
  16. package/dist/common/backends/webgl/shaders/vertexShader.d.ts +1 -0
  17. package/dist/common/backends/webgl/shaders/vertexShader.js +19 -0
  18. package/dist/common/backends/webgl/texture.d.ts +6 -0
  19. package/dist/common/backends/webgl/texture.js +94 -0
  20. package/dist/common/backends/webgl/types.d.ts +5 -0
  21. package/dist/common/backends/webgl/types.js +1 -0
  22. package/dist/common/backends/webgl/webgl.mock.js +8 -0
  23. package/dist/common/backends/webgl/webglUtils.d.ts +80 -0
  24. package/dist/common/backends/webgl/webglUtils.js +194 -0
  25. package/dist/common/canvasRenderUtils.d.ts +62 -0
  26. package/dist/{main/video → common}/canvasRenderUtils.js +100 -61
  27. package/dist/common/constants.d.ts +27 -0
  28. package/dist/common/constants.js +23 -0
  29. package/dist/common/index.d.ts +9 -0
  30. package/dist/common/index.js +9 -0
  31. package/dist/common/m3.d.ts +17 -0
  32. package/dist/common/m3.js +69 -0
  33. package/dist/common/tsconfig.tsbuildinfo +1 -0
  34. package/dist/common/types/media.d.ts +22 -0
  35. package/dist/common/types/media.js +1 -0
  36. package/dist/common/types/messageEvents.d.ts +31 -0
  37. package/dist/common/types/messageEvents.js +1 -0
  38. package/dist/common/types/processor.d.ts +15 -0
  39. package/dist/common/types/processor.js +1 -0
  40. package/dist/common/types/render.d.ts +38 -0
  41. package/dist/common/types/render.js +1 -0
  42. package/dist/common/types/segmentation.d.ts +99 -0
  43. package/dist/common/types/segmentation.js +4 -0
  44. package/dist/common/types/utils.d.ts +19 -0
  45. package/dist/common/types/utils.js +1 -0
  46. package/dist/common/utils.d.ts +35 -0
  47. package/dist/common/utils.js +129 -0
  48. package/dist/main/benchUtils.d.ts +3 -2
  49. package/dist/main/benchUtils.js +7 -6
  50. package/dist/main/index.d.ts +2 -0
  51. package/dist/main/index.js +2 -0
  52. package/dist/main/process.d.ts +1 -11
  53. package/dist/main/process.js +0 -20
  54. package/dist/main/tsconfig.tsbuildinfo +1 -1
  55. package/dist/main/types.d.ts +2 -2
  56. package/dist/main/video/canvasTransform.d.ts +14 -5
  57. package/dist/main/video/canvasTransform.js +101 -86
  58. package/dist/main/video/constants.d.ts +0 -6
  59. package/dist/main/video/constants.js +0 -6
  60. package/dist/main/video/index.d.ts +2 -2
  61. package/dist/main/video/index.js +2 -2
  62. package/dist/main/video/load.d.ts +0 -14
  63. package/dist/main/video/load.js +0 -8
  64. package/dist/main/video/segmenters/mediapipe.d.ts +9 -11
  65. package/dist/main/video/segmenters/mediapipe.js +127 -77
  66. package/dist/main/video/typeGuards.d.ts +0 -1
  67. package/dist/main/video/typeGuards.js +2 -8
  68. package/dist/main/video/types.d.ts +19 -61
  69. package/dist/main/video/types.js +1 -20
  70. package/dist/main/video/utils.d.ts +5 -12
  71. package/dist/main/video/utils.js +5 -152
  72. package/dist/main/video/video.js +1 -0
  73. package/dist/main/video/videoStreamTrackProcessor.js +7 -4
  74. package/dist/workers/global.d.js +0 -0
  75. package/dist/workers/mediaWorker.d.ts +1 -0
  76. package/dist/workers/mediaWorker.js +188 -0
  77. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  78. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +23 -14
  80. package/dist/main/video/canvasRenderUtils.d.ts +0 -27
  81. package/dist/main/video/segmenters/index.d.ts +0 -1
  82. package/dist/main/video/segmenters/index.js +0 -1
  83. package/dist/workers/index.js +0 -1
  84. /package/dist/{workers/index.d.ts → common/backends/webgl/webgl.mock.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 18.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - bfb8058: Replace @mediapipe/selfie_segmentation with @mediapipe/tasks-vision
8
+
9
+ - Replace @mediapipe/selfie_segmentation with @mediapipe/tasks-vision
10
+ - Move video processing into WebWorker
11
+ - Replace Canvas 2D with WebGL implementation
12
+ - Replace Gaussian Blur with Dual filtering blur
13
+ - Add Segmentation Mask Smoothing
14
+
15
+ ### Minor Changes
16
+
17
+ - 0a5a136: Fix getting the correct size of the MediaStream from a video element
18
+ - 6b75621: Return the processing result when no output provided
19
+ - 218a2c0: Refactor lazy props
20
+ - 9bcfc68: Avoid processing a frame which is disposed
21
+ - 1543a66: Allow passing Background Image Binary
22
+
23
+ - Add `update()` API to allow passing Background Image for background
24
+ replacement
25
+
26
+ - 68e8ec1: Standardize input size and ratio
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [bfb8058]
31
+ - @pexip/utils@16.11.0
32
+
33
+ ## 17.4.0
34
+
35
+ ### Minor Changes
36
+
37
+ - 24fe7e1: Fix lint related errors
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [4a1908a]
42
+ - Updated dependencies [fec1c90]
43
+ - Updated dependencies [ec134d1]
44
+ - Updated dependencies [24fe7e1]
45
+ - @pexip/utils@16.10.0
46
+ - @pexip/denoise@16.4.0
47
+
3
48
  ## 17.3.0
4
49
 
5
50
  ### Minor Changes
@@ -0,0 +1,11 @@
1
+ import type { RendererOptions } from '../../types/render';
2
+ import type { TextureInfo } from './types';
3
+ export declare const createBlendRenderer: (processor: OffscreenCanvas) => {
4
+ render: ({ mask, prevMask, foreground, background, }: {
5
+ prevMask?: TextureInfo | undefined;
6
+ mask: TextureInfo;
7
+ foreground: TextureInfo;
8
+ background: TextureInfo;
9
+ }, options: RendererOptions) => TextureInfo;
10
+ release: () => void;
11
+ };
@@ -0,0 +1,104 @@
1
+ import { assert } from '../../utils';
2
+ import { m3 } from '../../m3';
3
+ import { createGLProgram, createAndSetupTexture } from './webglUtils';
4
+ import { vertexShaderSource } from './shaders/vertexShader';
5
+ import { blendFragmentShaderSource } from './shaders/blendFragmentShader';
6
+ export const createBlendRenderer = (processor) => {
7
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
8
+ attribs: {
9
+ position: 'a_position',
10
+ texCoord: 'a_texCoord',
11
+ },
12
+ uniforms: {
13
+ matrix: 'u_matrix',
14
+ frameBackground: 'u_frame_background',
15
+ frameForeground: 'u_frame_foreground',
16
+ mask: 'u_mask',
17
+ maskCombineRatio: 'u_mask_combine_ratio',
18
+ foregroundThreshold: 'u_foreground_threshold',
19
+ prevMask: 'u_mask_prev',
20
+ },
21
+ vertexSrc: vertexShaderSource,
22
+ fragmentSrc: blendFragmentShaderSource,
23
+ });
24
+ gl.enable(gl.BLEND);
25
+ const vao = gl.createVertexArray();
26
+ gl.bindVertexArray(vao);
27
+ // Position
28
+ const positionBuffer = gl.createBuffer();
29
+ gl.enableVertexAttribArray(attribLocations.position);
30
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
31
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
32
+ gl.bufferData(gl.ARRAY_BUFFER,
33
+ // prettier-ignore
34
+ new Float32Array([
35
+ 0, 0,
36
+ 0, 1,
37
+ 1, 0,
38
+ 1, 0,
39
+ 0, 1,
40
+ 1, 1,
41
+ ]), gl.STATIC_DRAW);
42
+ // Texture coordinates for the rectangle
43
+ const texCoordBuffer = gl.createBuffer();
44
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
45
+ gl.bufferData(gl.ARRAY_BUFFER,
46
+ // prettier-ignore
47
+ new Float32Array([
48
+ 0, 0,
49
+ 0, 1,
50
+ 1, 0,
51
+ 1, 0,
52
+ 0, 1,
53
+ 1, 1,
54
+ ]), gl.STATIC_DRAW);
55
+ gl.enableVertexAttribArray(attribLocations.texCoord);
56
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
57
+ const outputTexture = createAndSetupTexture(gl);
58
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, processor.width, processor.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
59
+ const frameBuffer = gl.createFramebuffer();
60
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
61
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
62
+ return {
63
+ render: ({ mask, prevMask, foreground, background, }, options) => {
64
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
65
+ gl.viewport(0, 0, processor.width, processor.height);
66
+ gl.useProgram(program);
67
+ gl.bindVertexArray(vao);
68
+ // Textures
69
+ gl.activeTexture(gl.TEXTURE0);
70
+ gl.uniform1i(uniformLocations.frameForeground, 0);
71
+ gl.bindTexture(gl.TEXTURE_2D, foreground.texture);
72
+ gl.activeTexture(gl.TEXTURE1);
73
+ gl.uniform1i(uniformLocations.frameBackground, 1);
74
+ gl.bindTexture(gl.TEXTURE_2D, background.texture);
75
+ gl.activeTexture(gl.TEXTURE2);
76
+ gl.uniform1i(uniformLocations.mask, 2);
77
+ gl.bindTexture(gl.TEXTURE_2D, mask.texture);
78
+ gl.activeTexture(gl.TEXTURE3);
79
+ gl.uniform1i(uniformLocations.prevMask, 3);
80
+ gl.bindTexture(gl.TEXTURE_2D, prevMask?.texture ?? mask.texture);
81
+ // No Flip Y Coordinate for drawing on the frame buffer
82
+ // Project, position and scale to full-size of the canvas
83
+ const matrix = m3.projection(1, 1, 1);
84
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
85
+ gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
86
+ gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
87
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
88
+ assert(outputTexture);
89
+ return {
90
+ texture: outputTexture,
91
+ width: processor.width,
92
+ height: processor.height,
93
+ };
94
+ },
95
+ release: () => {
96
+ gl.deleteBuffer(positionBuffer);
97
+ gl.deleteBuffer(texCoordBuffer);
98
+ gl.deleteTexture(outputTexture);
99
+ gl.deleteFramebuffer(frameBuffer);
100
+ gl.deleteVertexArray(vao);
101
+ release();
102
+ },
103
+ };
104
+ };
@@ -0,0 +1,5 @@
1
+ import type { TextureInfo } from './types';
2
+ export declare const createBlurRenderer: (processor: OffscreenCanvas, pass: number) => {
3
+ render: (frame: TextureInfo, pass: number) => TextureInfo;
4
+ release: () => void;
5
+ };
@@ -0,0 +1,153 @@
1
+ import { assert, clamping } from '../../utils';
2
+ import { m3 } from '../../m3';
3
+ import { createAndSetupTexture, createGLProgram } from './webglUtils';
4
+ import { vertexShaderSource } from './shaders/vertexShader';
5
+ import { dualFilterBlurFragmentShaderSource } from './shaders/dualFilterBlurFragmentShader';
6
+ const clampBlurAmount = clamping(1, 9);
7
+ export const createBlurRenderer = (processor, pass) => {
8
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
9
+ attribs: {
10
+ position: 'a_position',
11
+ texCoord: 'a_texCoord',
12
+ },
13
+ uniforms: {
14
+ matrix: 'u_matrix',
15
+ downsampling: 'u_downsampling',
16
+ frame: 'u_frame',
17
+ },
18
+ vertexSrc: vertexShaderSource,
19
+ fragmentSrc: dualFilterBlurFragmentShaderSource,
20
+ });
21
+ const vao = gl.createVertexArray();
22
+ gl.bindVertexArray(vao);
23
+ // Position
24
+ const positionBuffer = gl.createBuffer();
25
+ gl.enableVertexAttribArray(attribLocations.position);
26
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
27
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
28
+ // Put a unit quad in the buffer
29
+ gl.bufferData(gl.ARRAY_BUFFER,
30
+ // prettier-ignore
31
+ new Float32Array([
32
+ 0, 0,
33
+ 0, 1,
34
+ 1, 0,
35
+ 1, 0,
36
+ 0, 1,
37
+ 1, 1,
38
+ ]), gl.STATIC_DRAW);
39
+ // Texture coordinates for the rectangle
40
+ const texCoordBuffer = gl.createBuffer();
41
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
42
+ gl.bufferData(gl.ARRAY_BUFFER,
43
+ // prettier-ignore
44
+ new Float32Array([
45
+ 0, 0,
46
+ 0, 1,
47
+ 1, 0,
48
+ 1, 0,
49
+ 0, 1,
50
+ 1, 1,
51
+ ]), gl.STATIC_DRAW);
52
+ gl.enableVertexAttribArray(attribLocations.texCoord);
53
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
54
+ const setFrameBuffer = (frameBuffer, width, height) => {
55
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
56
+ gl.viewport(0, 0, width, height);
57
+ };
58
+ const createFramebufferAndTexture = (idx) => {
59
+ const texture = createAndSetupTexture(gl);
60
+ assert(texture);
61
+ // Avoid fractional value
62
+ const width = Math.trunc(processor.width / Math.pow(2, idx));
63
+ const height = Math.trunc(processor.height / Math.pow(2, idx));
64
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
65
+ const frameBuffer = gl.createFramebuffer();
66
+ assert(frameBuffer);
67
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
68
+ // Attach a texture to it.
69
+ const attachmentPoint = gl.COLOR_ATTACHMENT0;
70
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, texture, 0);
71
+ return { texture, frameBuffer, width, height };
72
+ };
73
+ const generateBlurBuffers = (pass, offset = 0) => {
74
+ const length = Math.max(clampBlurAmount(offset + pass) - offset, 0);
75
+ return Array.from({
76
+ length,
77
+ }).map((_, currentIdx) => createFramebufferAndTexture(offset + currentIdx));
78
+ };
79
+ // Generate multiple FrameBuffers, textures and the widths and heights are of size
80
+ // of half of the previous one. E.g. 640x480 -> 320x240 -> 160x120 -> 80x60
81
+ const blurBuffers = generateBlurBuffers(pass + 1);
82
+ return {
83
+ render: (frame, pass) => {
84
+ gl.useProgram(program);
85
+ gl.bindVertexArray(vao);
86
+ // Frame texture
87
+ gl.activeTexture(gl.TEXTURE0);
88
+ gl.uniform1i(uniformLocations.frame, 0);
89
+ gl.bindTexture(gl.TEXTURE_2D, frame.texture);
90
+ // Dual filter blur
91
+ // Downsampling
92
+ gl.uniform1i(uniformLocations.downsampling, 1);
93
+ // Adjust pass number accordingly
94
+ const currentPass = pass + 1;
95
+ if (currentPass > blurBuffers.length) {
96
+ generateBlurBuffers(currentPass - blurBuffers.length, blurBuffers.length).forEach(buffer => blurBuffers.push(buffer));
97
+ }
98
+ while (currentPass < blurBuffers.length) {
99
+ const buffer = blurBuffers.pop();
100
+ gl.deleteTexture(buffer?.texture ?? null);
101
+ gl.deleteFramebuffer(buffer?.frameBuffer ?? null);
102
+ }
103
+ // Skip the first pass
104
+ for (let i = 1; i < currentPass; i++) {
105
+ const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
106
+ // Setup to draw to the frame buffer
107
+ setFrameBuffer(frameBuffer, width, height);
108
+ const matrix = m3.projection(1, 1, 1);
109
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
110
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
111
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
112
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
113
+ throw new Error(`downsampling FrameBuffer Status: ${status}`);
114
+ }
115
+ // for the next draw, use the texture we just rendered to.
116
+ gl.bindTexture(gl.TEXTURE_2D, texture);
117
+ }
118
+ // Upsampling
119
+ gl.uniform1i(uniformLocations.downsampling, 0);
120
+ // Skip the first pass
121
+ for (let i = pass - 1; i >= 0; i--) {
122
+ const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
123
+ // console.log(`Drawing upsampling pass ${i}, ${width}x${height}`);
124
+ // Setup to draw to the frame buffer
125
+ setFrameBuffer(frameBuffer, width, height);
126
+ const matrix = m3.projection(1, 1, 1);
127
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
128
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
129
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
130
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
131
+ throw new Error(`Upsampling FrameBuffer Status: ${status}`);
132
+ }
133
+ // for the next draw, use the texture we just rendered to.
134
+ gl.bindTexture(gl.TEXTURE_2D, texture);
135
+ }
136
+ return {
137
+ texture: blurBuffers[0]?.texture ?? frame.texture,
138
+ width: blurBuffers[0]?.width ?? frame.width,
139
+ height: blurBuffers[0]?.height ?? frame.height,
140
+ };
141
+ },
142
+ release: () => {
143
+ blurBuffers.forEach(({ frameBuffer, texture }) => {
144
+ gl.deleteTexture(texture);
145
+ gl.deleteFramebuffer(frameBuffer);
146
+ });
147
+ gl.deleteBuffer(positionBuffer);
148
+ gl.deleteBuffer(texCoordBuffer);
149
+ gl.deleteVertexArray(vao);
150
+ release();
151
+ },
152
+ };
153
+ };
@@ -0,0 +1,5 @@
1
+ import type { TextureInfo } from './types';
2
+ export declare const createCanvasRenderer: (processor: OffscreenCanvas) => {
3
+ render: (texture: TextureInfo) => void;
4
+ release: () => void;
5
+ };
@@ -0,0 +1,77 @@
1
+ import { m3 } from '../../m3';
2
+ import { createGLProgram } from './webglUtils';
3
+ import { vertexShaderSource } from './shaders/vertexShader';
4
+ import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
5
+ export const createCanvasRenderer = (processor) => {
6
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
7
+ attribs: {
8
+ position: 'a_position',
9
+ texCoord: 'a_texCoord',
10
+ },
11
+ uniforms: {
12
+ matrix: 'u_matrix',
13
+ texture: 'u_texture',
14
+ },
15
+ vertexSrc: vertexShaderSource,
16
+ fragmentSrc: textureFragmentShaderSource,
17
+ });
18
+ const vao = gl.createVertexArray();
19
+ gl.bindVertexArray(vao);
20
+ // Position
21
+ const positionBuffer = gl.createBuffer();
22
+ gl.enableVertexAttribArray(attribLocations.position);
23
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
24
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
25
+ // Put a unit quad in the buffer
26
+ gl.bufferData(gl.ARRAY_BUFFER,
27
+ // prettier-ignore
28
+ new Float32Array([
29
+ 0, 0,
30
+ 0, 1,
31
+ 1, 0,
32
+ 1, 0,
33
+ 0, 1,
34
+ 1, 1,
35
+ ]), gl.STATIC_DRAW);
36
+ // Texture coordinates for the rectangle
37
+ const texCoordBuffer = gl.createBuffer();
38
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
39
+ gl.bufferData(gl.ARRAY_BUFFER,
40
+ // prettier-ignore
41
+ new Float32Array([
42
+ 0, 0,
43
+ 0, 1,
44
+ 1, 0,
45
+ 1, 0,
46
+ 0, 1,
47
+ 1, 1,
48
+ ]), gl.STATIC_DRAW);
49
+ gl.enableVertexAttribArray(attribLocations.texCoord);
50
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
51
+ return {
52
+ render: (texture) => {
53
+ // Draw on the Canvas
54
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
55
+ gl.viewport(0, 0, processor.width, processor.height);
56
+ gl.clearColor(0, 0, 0, 1);
57
+ gl.clear(gl.COLOR_BUFFER_BIT);
58
+ gl.useProgram(program);
59
+ gl.bindVertexArray(vao);
60
+ // Texture Unit setup
61
+ gl.uniform1i(uniformLocations.texture, 0);
62
+ // Project, position and scale to full-size of the canvas
63
+ const matrix = m3.projection(1, 1);
64
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
65
+ // Frame texture
66
+ gl.activeTexture(gl.TEXTURE0);
67
+ gl.bindTexture(gl.TEXTURE_2D, texture.texture);
68
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
69
+ },
70
+ release: () => {
71
+ gl.deleteBuffer(positionBuffer);
72
+ gl.deleteBuffer(texCoordBuffer);
73
+ gl.deleteVertexArray(vao);
74
+ release();
75
+ },
76
+ };
77
+ };
@@ -0,0 +1,8 @@
1
+ import type { Renderer, RendererOptions } from '../../types/render';
2
+ import type { ImageMapIterable } from '../../types/media';
3
+ interface Options extends Partial<RendererOptions> {
4
+ backgroundImageInits?: ImageMapIterable;
5
+ onError?: (error: Error) => void;
6
+ }
7
+ export declare const createRenderer: (canvas?: OffscreenCanvas, { backgroundBlurAmount, backgroundImageUrl, edgeBlurAmount, effects, foregroundThreshold, maskCombineRatio, onError, backgroundImageInits, }?: Options) => Renderer;
8
+ export {};
@@ -0,0 +1,126 @@
1
+ import { PROCESSING_WIDTH, PROCESSING_HEIGHT, EDGE_BLUR_AMOUNT, BACKGROUND_BLUR_AMOUNT, FOREGROUND_THRESHOLD, MASK_COMBINE_RATIO, RENDER_EFFECT, } from '../../constants';
2
+ import { createObjectUpdater, createRemoteImageBitmap } from '../../utils';
3
+ import { toTextureInfo } from './webglUtils';
4
+ import { createTextureRenderer } from './texture';
5
+ import { createBlurRenderer } from './blur';
6
+ import { createBlendRenderer } from './blender';
7
+ import { createCanvasRenderer } from './canvas';
8
+ const createLazyProps = (stateBuilder) => {
9
+ const props = {};
10
+ return new Proxy(props, {
11
+ get(target, p, receiver) {
12
+ const prop = target[p];
13
+ if (!prop) {
14
+ target[p] = stateBuilder[p]?.(props);
15
+ }
16
+ return Reflect.get(target, p, receiver);
17
+ },
18
+ });
19
+ };
20
+ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PROCESSING_HEIGHT), { backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, backgroundImageUrl = '', edgeBlurAmount = EDGE_BLUR_AMOUNT, effects = RENDER_EFFECT.None, foregroundThreshold = FOREGROUND_THRESHOLD, maskCombineRatio = MASK_COMBINE_RATIO, onError, backgroundImageInits, } = {}) => {
21
+ const rendererCreator = {
22
+ videoFrameRenderer: () => createTextureRenderer(canvas),
23
+ canvasRenderer: () => createCanvasRenderer(canvas),
24
+ backgroundImageRenderer: () => createTextureRenderer(canvas),
25
+ blendRenderer: () => createBlendRenderer(canvas),
26
+ blurRenderer: () => createBlurRenderer(canvas, props.backgroundBlurAmount),
27
+ maskBlurRenderer: () => createBlurRenderer(canvas, props.edgeBlurAmount),
28
+ release: (states) => () => Object.keys(states).forEach(key => key !== 'release' &&
29
+ states[key]?.release()),
30
+ };
31
+ let lazyProps = createLazyProps(rendererCreator);
32
+ const props = {
33
+ backgroundBlurAmount,
34
+ backgroundImageUrl,
35
+ edgeBlurAmount,
36
+ effects,
37
+ foregroundThreshold,
38
+ maskCombineRatio,
39
+ canvas,
40
+ backgroundImages: createRemoteImageBitmap(backgroundImageInits),
41
+ // TODO: refactor to avoid repeated codes
42
+ get videoFrameRenderer() {
43
+ return lazyProps.videoFrameRenderer;
44
+ },
45
+ get canvasRenderer() {
46
+ return lazyProps.canvasRenderer;
47
+ },
48
+ get backgroundImageRenderer() {
49
+ return lazyProps.backgroundImageRenderer;
50
+ },
51
+ get blendRenderer() {
52
+ return lazyProps.blendRenderer;
53
+ },
54
+ get blurRenderer() {
55
+ return lazyProps.blurRenderer;
56
+ },
57
+ // TODO: Impl edge blur
58
+ get maskBlurRenderer() {
59
+ return lazyProps.maskBlurRenderer;
60
+ },
61
+ };
62
+ const updateProps = createObjectUpdater(props);
63
+ const update = options => {
64
+ if (options.backgroundImage) {
65
+ props.backgroundImages.setImage(options.backgroundImage.key, options.backgroundImage.image);
66
+ }
67
+ updateProps(options);
68
+ };
69
+ const handleError = (error) => {
70
+ if (error instanceof Error) {
71
+ return onError?.(error);
72
+ }
73
+ throw error;
74
+ };
75
+ const init = options => {
76
+ if (options) {
77
+ update(options);
78
+ }
79
+ };
80
+ const render = (mask, frame, options) => {
81
+ if (options) {
82
+ updateProps(options);
83
+ }
84
+ const frameTexture = props.videoFrameRenderer.render(frame);
85
+ let background = frameTexture;
86
+ if (props.effects === 'blur' && props.backgroundBlurAmount) {
87
+ background = props.blurRenderer.render(frameTexture, props.backgroundBlurAmount);
88
+ }
89
+ else if (props?.effects === 'overlay' && props.backgroundImageUrl) {
90
+ // Keep rendering with video frame until we get the background image
91
+ const backgroundImage = props.backgroundImages.getImage(props.backgroundImageUrl);
92
+ if (backgroundImage) {
93
+ background = props.backgroundImageRenderer.render({
94
+ frame: backgroundImage,
95
+ timestamp: performance.now(),
96
+ width: backgroundImage.width,
97
+ height: backgroundImage.height,
98
+ });
99
+ }
100
+ else {
101
+ props.backgroundImages
102
+ .tryFetchingImage(props.backgroundImageUrl)
103
+ .catch(error => {
104
+ handleError(new Error(`FailedToLoadBackgroundImage: ${props.backgroundImageUrl}`, { cause: error }));
105
+ });
106
+ }
107
+ }
108
+ const blendedFrameTexture = props.blendRenderer.render({
109
+ prevMask: props.prevMask && toTextureInfo(props.prevMask),
110
+ mask: toTextureInfo(mask),
111
+ foreground: frameTexture,
112
+ background,
113
+ }, props);
114
+ props.canvasRenderer.render(blendedFrameTexture);
115
+ props.prevMask?.close();
116
+ props.prevMask = mask.clone();
117
+ };
118
+ const release = () => {
119
+ lazyProps.release();
120
+ lazyProps = createLazyProps(rendererCreator);
121
+ props.backgroundImages.clear();
122
+ props.prevMask?.close();
123
+ props.prevMask = undefined;
124
+ };
125
+ return { init, render, release, update };
126
+ };
@@ -0,0 +1 @@
1
+ export declare const blendFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_mask_prev;\nuniform sampler2D u_frame_foreground;\nuniform sampler2D u_frame_background;\n\n// control flags\nuniform float u_mask_combine_ratio;\nuniform float u_foreground_threshold;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\n// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377\nfloat SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {\n float prevMaskValue = texture(prevMask, uv).r;\n float newMaskValue = texture(currMask, uv).r;\n\n /*\n * Assume p := newMaskValue\n * H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)\n * uncertainty alpha(p) =\n * Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the\n * uncertainty]\n *\n * The following polynomial approximates uncertainty alpha as a\n * function of (p + 0.5):\n */\n const float c1 = 5.68842;\n const float c2 = -0.748699;\n const float c3 = -57.8051;\n const float c4 = 291.309;\n const float c5 = -624.717;\n float t = newMaskValue - 0.5;\n float x = t * t;\n\n float uncertainty =\n 1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));\n\n float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *\n (uncertainty * ratio);\n return outputValue;\n}\n\nvoid main() {\n vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));\n\n vec4 background = texture(u_frame_background, v_texCoord);\n vec4 foreground = texture(u_frame_foreground, v_texCoord);\n float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);\n float filteredMask = maskAlpha;\n if (maskAlpha < u_foreground_threshold) {\n filteredMask = 0.0;\n }\n\n outColor = mix(background, foreground, filteredMask);\n}\n";
@@ -0,0 +1,63 @@
1
+ export const blendFragmentShaderSource = `#version 300 es
2
+ precision mediump float;
3
+
4
+ // our textures
5
+ uniform sampler2D u_mask;
6
+ uniform sampler2D u_mask_prev;
7
+ uniform sampler2D u_frame_foreground;
8
+ uniform sampler2D u_frame_background;
9
+
10
+ // control flags
11
+ uniform float u_mask_combine_ratio;
12
+ uniform float u_foreground_threshold;
13
+
14
+ in vec2 v_texCoord;
15
+
16
+ // we need to declare an output for the fragment shader
17
+ out vec4 outColor;
18
+
19
+ // Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377
20
+ float SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {
21
+ float prevMaskValue = texture(prevMask, uv).r;
22
+ float newMaskValue = texture(currMask, uv).r;
23
+
24
+ /*
25
+ * Assume p := newMaskValue
26
+ * H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)
27
+ * uncertainty alpha(p) =
28
+ * Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the
29
+ * uncertainty]
30
+ *
31
+ * The following polynomial approximates uncertainty alpha as a
32
+ * function of (p + 0.5):
33
+ */
34
+ const float c1 = 5.68842;
35
+ const float c2 = -0.748699;
36
+ const float c3 = -57.8051;
37
+ const float c4 = 291.309;
38
+ const float c5 = -624.717;
39
+ float t = newMaskValue - 0.5;
40
+ float x = t * t;
41
+
42
+ float uncertainty =
43
+ 1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));
44
+
45
+ float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *
46
+ (uncertainty * ratio);
47
+ return outputValue;
48
+ }
49
+
50
+ void main() {
51
+ vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));
52
+
53
+ vec4 background = texture(u_frame_background, v_texCoord);
54
+ vec4 foreground = texture(u_frame_foreground, v_texCoord);
55
+ float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);
56
+ float filteredMask = maskAlpha;
57
+ if (maskAlpha < u_foreground_threshold) {
58
+ filteredMask = 0.0;
59
+ }
60
+
61
+ outColor = mix(background, foreground, filteredMask);
62
+ }
63
+ `;
@@ -0,0 +1 @@
1
+ export declare const dualFilterBlurFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_frame;\n\n// control flags\nuniform int u_downsampling;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvec4 Downsample(sampler2D image, vec2 uv, vec2 halfpixel) {\n vec4 sum = texture(image, uv) * 4.0;\n sum += texture(image, uv - halfpixel.xy);\n sum += texture(image, uv + halfpixel.xy);\n sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y));\n sum += texture(image, uv - vec2(halfpixel.x, -halfpixel.y));\n return sum / 8.0;\n}\n\nvec4 Upsample(sampler2D image, vec2 uv, vec2 halfpixel) {\n vec4 sum = texture(image, uv + vec2(-halfpixel.x * 2.0, 0.0));\n sum += texture(image, uv + vec2(-halfpixel.x, halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(0.0, halfpixel.y * 2.0));\n sum += texture(image, uv + vec2(halfpixel.x, halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(halfpixel.x * 2.0, 0.0));\n sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(0.0, -halfpixel.y * 2.0));\n sum += texture(image, uv + vec2(-halfpixel.x, -halfpixel.y)) * 2.0;\n return sum / 12.0;\n}\n\nvoid main() {\n vec2 halfpixel = 0.5 / vec2(textureSize(u_frame, 0));\n\n if (u_downsampling == 1) {\n outColor = Downsample(u_frame, v_texCoord, halfpixel);\n return;\n }\n outColor = Upsample(u_frame, v_texCoord, halfpixel);\n}\n";
@@ -0,0 +1,45 @@
1
+ export const dualFilterBlurFragmentShaderSource = `#version 300 es
2
+ precision mediump float;
3
+
4
+ // our textures
5
+ uniform sampler2D u_frame;
6
+
7
+ // control flags
8
+ uniform int u_downsampling;
9
+
10
+ in vec2 v_texCoord;
11
+
12
+ // we need to declare an output for the fragment shader
13
+ out vec4 outColor;
14
+
15
+ vec4 Downsample(sampler2D image, vec2 uv, vec2 halfpixel) {
16
+ vec4 sum = texture(image, uv) * 4.0;
17
+ sum += texture(image, uv - halfpixel.xy);
18
+ sum += texture(image, uv + halfpixel.xy);
19
+ sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y));
20
+ sum += texture(image, uv - vec2(halfpixel.x, -halfpixel.y));
21
+ return sum / 8.0;
22
+ }
23
+
24
+ vec4 Upsample(sampler2D image, vec2 uv, vec2 halfpixel) {
25
+ vec4 sum = texture(image, uv + vec2(-halfpixel.x * 2.0, 0.0));
26
+ sum += texture(image, uv + vec2(-halfpixel.x, halfpixel.y)) * 2.0;
27
+ sum += texture(image, uv + vec2(0.0, halfpixel.y * 2.0));
28
+ sum += texture(image, uv + vec2(halfpixel.x, halfpixel.y)) * 2.0;
29
+ sum += texture(image, uv + vec2(halfpixel.x * 2.0, 0.0));
30
+ sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y)) * 2.0;
31
+ sum += texture(image, uv + vec2(0.0, -halfpixel.y * 2.0));
32
+ sum += texture(image, uv + vec2(-halfpixel.x, -halfpixel.y)) * 2.0;
33
+ return sum / 12.0;
34
+ }
35
+
36
+ void main() {
37
+ vec2 halfpixel = 0.5 / vec2(textureSize(u_frame, 0));
38
+
39
+ if (u_downsampling == 1) {
40
+ outColor = Downsample(u_frame, v_texCoord, halfpixel);
41
+ return;
42
+ }
43
+ outColor = Upsample(u_frame, v_texCoord, halfpixel);
44
+ }
45
+ `;
@@ -0,0 +1 @@
1
+ export declare const textureFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// Passed in from the vertex shader.\nin vec2 v_texCoord;\n\n// The texture.\nuniform sampler2D u_texture;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvoid main() {\n outColor = texture(u_texture, v_texCoord);\n}\n";