@pexip/media-processor 18.3.0 → 18.4.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 (30) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +26 -28
  3. package/dist/common/backends/webgl/blender.d.ts +5 -4
  4. package/dist/common/backends/webgl/blender.js +4 -9
  5. package/dist/common/backends/webgl/blur.d.ts +3 -0
  6. package/dist/common/backends/webgl/blur.js +3 -0
  7. package/dist/common/backends/webgl/canvas.d.ts +3 -0
  8. package/dist/common/backends/webgl/canvas.js +3 -0
  9. package/dist/common/backends/webgl/renderer.js +18 -6
  10. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -1
  11. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +1 -43
  12. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +1 -0
  13. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +57 -0
  14. package/dist/common/backends/webgl/smoothingMask.d.ts +12 -0
  15. package/dist/common/backends/webgl/smoothingMask.js +100 -0
  16. package/dist/common/backends/webgl/texture.d.ts +3 -0
  17. package/dist/common/backends/webgl/texture.js +3 -0
  18. package/dist/common/backends/webgl/textureToTexture.d.ts +8 -0
  19. package/dist/common/backends/webgl/textureToTexture.js +92 -0
  20. package/dist/common/tsconfig.tsbuildinfo +1 -1
  21. package/dist/common/types/segmentation.d.ts +4 -2
  22. package/dist/main/tsconfig.tsbuildinfo +1 -1
  23. package/dist/main/video/segmenters/mediapipe.d.ts +19 -0
  24. package/dist/main/video/segmenters/mediapipe.js +11 -3
  25. package/dist/main/video/videoStreamTrackProcessor.d.ts +6 -0
  26. package/dist/main/video/videoStreamTrackProcessor.js +6 -0
  27. package/dist/workers/mediaWorker.js +36 -22
  28. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  29. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +2 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 18.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4bfce08: Decouple blender
8
+ - 31e4c17: Update media-processor's Readme
9
+
10
+ ### Patch Changes
11
+
12
+ - 36d5672: Remove offscreencanvas types
13
+ - @pexip/utils@16.12.0
14
+
3
15
  ## 18.3.0
4
16
 
5
17
  ## 18.2.0
package/README.md CHANGED
@@ -6,7 +6,7 @@ An package for media data processing using Web API.
6
6
 
7
7
  `npm install @pexip/media-processor`
8
8
 
9
- ## Examples
9
+ ## Usages
10
10
 
11
11
  ### Use Analyzer to get the MediaStream data
12
12
 
@@ -105,38 +105,36 @@ await audioGraph.release();
105
105
  ### Use background blur
106
106
 
107
107
  ```typescript
108
- const PROCESSING_WIDTH = 768;
109
- const PROCESSING_HEIGHT = 432;
110
-
108
+ import {
109
+ createSegmenter,
110
+ createCanvasTransform,
111
+ createVideoTrackProcessor,
112
+ createVideoTrackProcessorWithFallback,
113
+ } from '@pexip/media-processor';
114
+
115
+ // Grab the user's camera stream
111
116
  const stream = await navigator.mediaDevices.getUserMedia({video: true});
112
117
 
113
- // Thus we can create a URL object to get the link to be used for the `gluePath`
114
- const selfieJs = new URL('@mediapipe/selfie_segmentation', import.meta);
115
- const segmenter = createMediapipeSegmenter('selfie_segmentation', {
116
- modelType: selfieModel,
117
- processingWidth: PROCESSING_WIDTH,
118
- processingHeight: PROCESSING_HEIGHT,
119
- gluePath: selfieJs.href,
120
- });
121
-
122
- const transformer = createCanvasTransform(segmenter, {
123
- effects: 'blur',
124
- width: mediaState.width,
125
- height: mediaState.height,
126
- });
127
-
128
- const getTrackProcessor = () => {
129
- // Feature detection if the browser has the `MediaStreamProcessor` API
130
- if ('MediaStreamTrackProcessor' in window) {
131
- return processAPI === 'Streams API'
132
- ? createVideoTrackProcessor() // Using the latest Streams API
133
- : createVideoTrackProcessorWithFallback(); // Using the fallback implementation
134
- }
135
- return createVideoTrackProcessorWithFallback(); // Using the fallback implementation
118
+ // Setting the path to that `@mediapipe/tasks-vision` assets
119
+ // It will be passed direct to
120
+ // [FilesetResolver.forVisionTasks()](https://ai.google.dev/edge/api/mediapipe/js/tasks-vision.filesetresolver#filesetresolverforvisiontasks)
121
+ const tasksVisionBasePath =
122
+ 'A base path to specify the directory the Wasm files should be loaded from';
123
+
124
+ const modelAsset = {
125
+ /**
126
+ * Path to mediapipe selfie segmentation model asset
127
+ */
128
+ path: 'A path to selfie segmentation model',
129
+ modelName: 'selfie' as const,
136
130
  };
137
131
 
138
- const videoProcessor = createVideoProcessor([transformer], getTrackProcessor());
132
+ const segmenter = createSegmenter(tasksVisionBasePath, {modelAssets});
133
+ // Create a processing transformer and set the effects to `blur`
134
+ const transformer = createCanvasTransform(segmenter, {effects: 'blur'});
135
+ const processor = createVideoProcessor([transformer]);
139
136
 
137
+ // Start the processor
140
138
  await videoProcessor.open();
141
139
 
142
140
  // Passing the raw MediaStream to apply the effects
@@ -1,11 +1,12 @@
1
- import type { RendererOptions } from '../../types/render';
2
1
  import type { TextureInfo } from './types';
2
+ /**
3
+ * Blend foreground and background according to the mask
4
+ */
3
5
  export declare const createBlendRenderer: (processor: OffscreenCanvas) => {
4
- render: ({ mask, prevMask, foreground, background, }: {
5
- prevMask?: TextureInfo | undefined;
6
+ render: ({ mask, foreground, background, }: {
6
7
  mask: TextureInfo;
7
8
  foreground: TextureInfo;
8
9
  background: TextureInfo;
9
- }, options: RendererOptions) => TextureInfo;
10
+ }) => TextureInfo;
10
11
  release: () => void;
11
12
  };
@@ -3,6 +3,9 @@ import { m3 } from '../../m3';
3
3
  import { createGLProgram, createAndSetupTexture } from './webglUtils';
4
4
  import { vertexShaderSource } from './shaders/vertexShader';
5
5
  import { blendFragmentShaderSource } from './shaders/blendFragmentShader';
6
+ /**
7
+ * Blend foreground and background according to the mask
8
+ */
6
9
  export const createBlendRenderer = (processor) => {
7
10
  const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
8
11
  attribs: {
@@ -14,9 +17,6 @@ export const createBlendRenderer = (processor) => {
14
17
  frameBackground: 'u_frame_background',
15
18
  frameForeground: 'u_frame_foreground',
16
19
  mask: 'u_mask',
17
- maskCombineRatio: 'u_mask_combine_ratio',
18
- foregroundThreshold: 'u_foreground_threshold',
19
- prevMask: 'u_mask_prev',
20
20
  },
21
21
  vertexSrc: vertexShaderSource,
22
22
  fragmentSrc: blendFragmentShaderSource,
@@ -60,7 +60,7 @@ export const createBlendRenderer = (processor) => {
60
60
  gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
61
61
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
62
62
  return {
63
- render: ({ mask, prevMask, foreground, background, }, options) => {
63
+ render: ({ mask, foreground, background, }) => {
64
64
  gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
65
65
  gl.viewport(0, 0, processor.width, processor.height);
66
66
  gl.useProgram(program);
@@ -75,15 +75,10 @@ export const createBlendRenderer = (processor) => {
75
75
  gl.activeTexture(gl.TEXTURE2);
76
76
  gl.uniform1i(uniformLocations.mask, 2);
77
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
78
  // No Flip Y Coordinate for drawing on the frame buffer
82
79
  // Project, position and scale to full-size of the canvas
83
80
  const matrix = m3.projection(1, 1, 1);
84
81
  gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
85
- gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
86
- gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
87
82
  gl.drawArrays(gl.TRIANGLES, 0, 6);
88
83
  assert(outputTexture);
89
84
  return {
@@ -1,4 +1,7 @@
1
1
  import type { TextureInfo } from './types';
2
+ /**
3
+ * Blur provided frame
4
+ */
2
5
  export declare const createBlurRenderer: (processor: OffscreenCanvas, pass: number) => {
3
6
  render: (frame: TextureInfo, pass: number) => TextureInfo;
4
7
  release: () => void;
@@ -4,6 +4,9 @@ import { createAndSetupTexture, createGLProgram } from './webglUtils';
4
4
  import { vertexShaderSource } from './shaders/vertexShader';
5
5
  import { dualFilterBlurFragmentShaderSource } from './shaders/dualFilterBlurFragmentShader';
6
6
  const clampBlurAmount = clamping(1, 9);
7
+ /**
8
+ * Blur provided frame
9
+ */
7
10
  export const createBlurRenderer = (processor, pass) => {
8
11
  const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
9
12
  attribs: {
@@ -1,4 +1,7 @@
1
1
  import type { TextureInfo } from './types';
2
+ /**
3
+ * Render texture to the processing canvas framebuffer
4
+ */
2
5
  export declare const createCanvasRenderer: (processor: OffscreenCanvas) => {
3
6
  render: (texture: TextureInfo) => void;
4
7
  release: () => void;
@@ -2,6 +2,9 @@ import { m3 } from '../../m3';
2
2
  import { createGLProgram } from './webglUtils';
3
3
  import { vertexShaderSource } from './shaders/vertexShader';
4
4
  import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
5
+ /**
6
+ * Render texture to the processing canvas framebuffer
7
+ */
5
8
  export const createCanvasRenderer = (processor) => {
6
9
  const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
7
10
  attribs: {
@@ -5,6 +5,8 @@ import { createTextureRenderer } from './texture';
5
5
  import { createBlurRenderer } from './blur';
6
6
  import { createBlendRenderer } from './blender';
7
7
  import { createCanvasRenderer } from './canvas';
8
+ import { createTextureToTextureRenderer } from './textureToTexture';
9
+ import { createSmoothingMaskRenderer } from './smoothingMask';
8
10
  const createLazyProps = (stateBuilder) => {
9
11
  const props = {};
10
12
  return new Proxy(props, {
@@ -25,6 +27,8 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
25
27
  blendRenderer: () => createBlendRenderer(canvas),
26
28
  blurRenderer: () => createBlurRenderer(canvas, props.backgroundBlurAmount),
27
29
  maskBlurRenderer: () => createBlurRenderer(canvas, props.edgeBlurAmount),
30
+ textureRenderer: () => createTextureToTextureRenderer(canvas),
31
+ smoothingMaskRenderer: () => createSmoothingMaskRenderer(canvas),
28
32
  release: (states) => () => Object.keys(states).forEach(key => key !== 'release' &&
29
33
  states[key]?.release()),
30
34
  };
@@ -58,6 +62,12 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
58
62
  get maskBlurRenderer() {
59
63
  return lazyProps.maskBlurRenderer;
60
64
  },
65
+ get textureRenderer() {
66
+ return lazyProps.textureRenderer;
67
+ },
68
+ get smoothingMaskRenderer() {
69
+ return lazyProps.smoothingMaskRenderer;
70
+ },
61
71
  };
62
72
  const updateProps = createObjectUpdater(props);
63
73
  const update = options => {
@@ -81,6 +91,10 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
81
91
  if (options) {
82
92
  updateProps(options);
83
93
  }
94
+ const smoothedMask = props.smoothingMaskRenderer.render({
95
+ mask: toTextureInfo(mask),
96
+ prevMask: props.prevMask,
97
+ }, props);
84
98
  const frameTexture = props.videoFrameRenderer.render(frame);
85
99
  let background = frameTexture;
86
100
  if (props.effects === 'blur' && props.backgroundBlurAmount) {
@@ -106,20 +120,18 @@ export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PR
106
120
  }
107
121
  }
108
122
  const blendedFrameTexture = props.blendRenderer.render({
109
- prevMask: props.prevMask && toTextureInfo(props.prevMask),
110
- mask: toTextureInfo(mask),
123
+ mask: smoothedMask,
111
124
  foreground: frameTexture,
112
125
  background,
113
- }, props);
126
+ });
114
127
  props.canvasRenderer.render(blendedFrameTexture);
115
- props.prevMask?.close();
116
- props.prevMask = mask.clone();
128
+ // Cloned the current smoothedMask so it can be fed back to the next round
129
+ props.prevMask = props.textureRenderer.render(smoothedMask);
117
130
  };
118
131
  const release = () => {
119
132
  lazyProps.release();
120
133
  lazyProps = createLazyProps(rendererCreator);
121
134
  props.backgroundImages.clear();
122
- props.prevMask?.close();
123
135
  props.prevMask = undefined;
124
136
  };
125
137
  return { init, render, release, update };
@@ -1 +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";
1
+ export declare const blendFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_frame_foreground;\nuniform sampler2D u_frame_background;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvoid main() {\n vec4 background = texture(u_frame_background, v_texCoord);\n vec4 foreground = texture(u_frame_foreground, v_texCoord);\n float filteredMask = texture(u_mask, v_texCoord).r;\n\n outColor = mix(background, foreground, filteredMask);\n}\n";
@@ -3,60 +3,18 @@ precision mediump float;
3
3
 
4
4
  // our textures
5
5
  uniform sampler2D u_mask;
6
- uniform sampler2D u_mask_prev;
7
6
  uniform sampler2D u_frame_foreground;
8
7
  uniform sampler2D u_frame_background;
9
8
 
10
- // control flags
11
- uniform float u_mask_combine_ratio;
12
- uniform float u_foreground_threshold;
13
-
14
9
  in vec2 v_texCoord;
15
10
 
16
11
  // we need to declare an output for the fragment shader
17
12
  out vec4 outColor;
18
13
 
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
14
  void main() {
51
- vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));
52
-
53
15
  vec4 background = texture(u_frame_background, v_texCoord);
54
16
  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
- }
17
+ float filteredMask = texture(u_mask, v_texCoord).r;
60
18
 
61
19
  outColor = mix(background, foreground, filteredMask);
62
20
  }
@@ -0,0 +1 @@
1
+ export declare const smoothingMaskFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_mask_prev;\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 float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);\n float backgroundMask = maskAlpha;\n if (backgroundMask < u_foreground_threshold) {\n backgroundMask = 0.0;\n }\n\n outColor = vec4(backgroundMask, backgroundMask, backgroundMask, backgroundMask);\n}\n";
@@ -0,0 +1,57 @@
1
+ export const smoothingMaskFragmentShaderSource = `#version 300 es
2
+ precision mediump float;
3
+
4
+ // our textures
5
+ uniform sampler2D u_mask;
6
+ uniform sampler2D u_mask_prev;
7
+
8
+ // control flags
9
+ uniform float u_mask_combine_ratio;
10
+ uniform float u_foreground_threshold;
11
+
12
+ in vec2 v_texCoord;
13
+
14
+ // we need to declare an output for the fragment shader
15
+ out vec4 outColor;
16
+
17
+ // Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377
18
+ float SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {
19
+ float prevMaskValue = texture(prevMask, uv).r;
20
+ float newMaskValue = texture(currMask, uv).r;
21
+
22
+ /*
23
+ * Assume p := newMaskValue
24
+ * H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)
25
+ * uncertainty alpha(p) =
26
+ * Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the
27
+ * uncertainty]
28
+ *
29
+ * The following polynomial approximates uncertainty alpha as a
30
+ * function of (p + 0.5):
31
+ */
32
+ const float c1 = 5.68842;
33
+ const float c2 = -0.748699;
34
+ const float c3 = -57.8051;
35
+ const float c4 = 291.309;
36
+ const float c5 = -624.717;
37
+ float t = newMaskValue - 0.5;
38
+ float x = t * t;
39
+
40
+ float uncertainty =
41
+ 1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));
42
+
43
+ float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *
44
+ (uncertainty * ratio);
45
+ return outputValue;
46
+ }
47
+
48
+ void main() {
49
+ float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);
50
+ float backgroundMask = maskAlpha;
51
+ if (backgroundMask < u_foreground_threshold) {
52
+ backgroundMask = 0.0;
53
+ }
54
+
55
+ outColor = vec4(backgroundMask, backgroundMask, backgroundMask, backgroundMask);
56
+ }
57
+ `;
@@ -0,0 +1,12 @@
1
+ import type { RendererOptions } from '../../types/render';
2
+ import type { TextureInfo } from './types';
3
+ /**
4
+ * Smooth the mask edge
5
+ */
6
+ export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas) => {
7
+ render: ({ mask, prevMask, }: {
8
+ prevMask?: TextureInfo | undefined;
9
+ mask: TextureInfo;
10
+ }, options: RendererOptions) => TextureInfo;
11
+ release: () => void;
12
+ };
@@ -0,0 +1,100 @@
1
+ import { assert } from '../../utils';
2
+ import { m3 } from '../../m3';
3
+ import { createGLProgram, createAndSetupTexture } from './webglUtils';
4
+ import { vertexShaderSource } from './shaders/vertexShader';
5
+ import { smoothingMaskFragmentShaderSource } from './shaders/smoothingMaskFragmentShader';
6
+ /**
7
+ * Smooth the mask edge
8
+ */
9
+ export const createSmoothingMaskRenderer = (processor) => {
10
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
11
+ attribs: {
12
+ position: 'a_position',
13
+ texCoord: 'a_texCoord',
14
+ },
15
+ uniforms: {
16
+ matrix: 'u_matrix',
17
+ mask: 'u_mask',
18
+ maskCombineRatio: 'u_mask_combine_ratio',
19
+ foregroundThreshold: 'u_foreground_threshold',
20
+ prevMask: 'u_mask_prev',
21
+ },
22
+ vertexSrc: vertexShaderSource,
23
+ fragmentSrc: smoothingMaskFragmentShaderSource,
24
+ });
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, }, options) => {
64
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
65
+ gl.viewport(0, 0, processor.width, processor.height);
66
+ gl.clearColor(0, 0, 0, 0);
67
+ gl.clear(gl.COLOR_BUFFER_BIT);
68
+ gl.useProgram(program);
69
+ gl.bindVertexArray(vao);
70
+ // Textures
71
+ gl.activeTexture(gl.TEXTURE0);
72
+ gl.uniform1i(uniformLocations.mask, 0);
73
+ gl.bindTexture(gl.TEXTURE_2D, mask.texture);
74
+ gl.activeTexture(gl.TEXTURE1);
75
+ gl.uniform1i(uniformLocations.prevMask, 1);
76
+ gl.bindTexture(gl.TEXTURE_2D, prevMask?.texture ?? mask.texture);
77
+ // No Flip Y Coordinate for drawing on the frame buffer
78
+ // Project, position and scale to full-size of the canvas
79
+ const matrix = m3.projection(1, 1, 1);
80
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
81
+ gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
82
+ gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
83
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
84
+ assert(outputTexture);
85
+ return {
86
+ texture: outputTexture,
87
+ width: processor.width,
88
+ height: processor.height,
89
+ };
90
+ },
91
+ release: () => {
92
+ gl.deleteBuffer(positionBuffer);
93
+ gl.deleteBuffer(texCoordBuffer);
94
+ gl.deleteTexture(outputTexture);
95
+ gl.deleteFramebuffer(frameBuffer);
96
+ gl.deleteVertexArray(vao);
97
+ release();
98
+ },
99
+ };
100
+ };
@@ -1,5 +1,8 @@
1
1
  import type { VideoFrameLike } from '../../types/media';
2
2
  import type { TextureInfo } from './types';
3
+ /**
4
+ * Render Video frame into texture
5
+ */
3
6
  export declare const createTextureRenderer: (processor: OffscreenCanvas) => {
4
7
  render: (video: VideoFrameLike) => TextureInfo;
5
8
  release: () => void;
@@ -3,6 +3,9 @@ import { m3 } from '../../m3';
3
3
  import { createAndSetupTexture, createGLProgram } from './webglUtils';
4
4
  import { vertexShaderSource } from './shaders/vertexShader';
5
5
  import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
6
+ /**
7
+ * Render Video frame into texture
8
+ */
6
9
  export const createTextureRenderer = (processor) => {
7
10
  const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
8
11
  attribs: {
@@ -0,0 +1,8 @@
1
+ import type { TextureInfo } from './types';
2
+ /**
3
+ * Render texture into another texture, i.e. clone
4
+ */
5
+ export declare const createTextureToTextureRenderer: (processor: OffscreenCanvas) => {
6
+ render: (inputTexture: TextureInfo) => TextureInfo;
7
+ release: () => void;
8
+ };
@@ -0,0 +1,92 @@
1
+ import { assert } from '../../utils';
2
+ import { m3 } from '../../m3';
3
+ import { createAndSetupTexture, createGLProgram } from './webglUtils';
4
+ import { vertexShaderSource } from './shaders/vertexShader';
5
+ import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
6
+ /**
7
+ * Render texture into another texture, i.e. clone
8
+ */
9
+ export const createTextureToTextureRenderer = (processor) => {
10
+ const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
11
+ attribs: {
12
+ position: 'a_position',
13
+ texCoord: 'a_texCoord',
14
+ },
15
+ uniforms: {
16
+ matrix: 'u_matrix',
17
+ texture: 'u_texture',
18
+ },
19
+ vertexSrc: vertexShaderSource,
20
+ fragmentSrc: textureFragmentShaderSource,
21
+ });
22
+ const vao = gl.createVertexArray();
23
+ gl.bindVertexArray(vao);
24
+ // Position
25
+ const positionBuffer = gl.createBuffer();
26
+ gl.enableVertexAttribArray(attribLocations.position);
27
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
28
+ gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
29
+ // Put a unit quad in the buffer
30
+ gl.bufferData(gl.ARRAY_BUFFER,
31
+ // prettier-ignore
32
+ new Float32Array([
33
+ 0, 0,
34
+ 0, 1,
35
+ 1, 0,
36
+ 1, 0,
37
+ 0, 1,
38
+ 1, 1,
39
+ ]), gl.STATIC_DRAW);
40
+ // Texture coordinates for the rectangle
41
+ const texCoordBuffer = gl.createBuffer();
42
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
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
+ gl.enableVertexAttribArray(attribLocations.texCoord);
54
+ gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
55
+ const outputTexture = createAndSetupTexture(gl);
56
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, processor.width, processor.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
57
+ const frameBuffer = gl.createFramebuffer();
58
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
59
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
60
+ return {
61
+ render: (inputTexture) => {
62
+ // Draw on the frame buffer
63
+ gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
64
+ gl.viewport(0, 0, processor.width, processor.height);
65
+ gl.clearColor(0, 0, 0, 1);
66
+ gl.clear(gl.COLOR_BUFFER_BIT);
67
+ gl.useProgram(program);
68
+ gl.bindVertexArray(vao);
69
+ // Texture Unit setup
70
+ gl.activeTexture(gl.TEXTURE0);
71
+ gl.uniform1i(uniformLocations.texture, 0);
72
+ gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture);
73
+ // Project, position and scale to full-size of the canvas
74
+ const matrix = m3.projection(1, 1, 1);
75
+ gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
76
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
77
+ assert(outputTexture);
78
+ return {
79
+ texture: outputTexture,
80
+ width: processor.width,
81
+ height: processor.height,
82
+ };
83
+ },
84
+ release: () => {
85
+ gl.deleteBuffer(positionBuffer);
86
+ gl.deleteBuffer(texCoordBuffer);
87
+ gl.deleteTexture(outputTexture);
88
+ gl.deleteVertexArray(vao);
89
+ release();
90
+ },
91
+ };
92
+ };