@pexip/media-processor 20.3.5 → 22.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.
- package/CHANGELOG.md +109 -0
- package/README.md +17 -10
- package/api-docs/README.mdx +69 -23
- package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
- package/api-docs/functions/createBenchmark.mdx +17 -5
- package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
- package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createVideoProcessor.mdx +3 -3
- package/api-docs/functions/createWindowedStats.mdx +23 -0
- package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/iou.mdx +19 -0
- package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
- package/api-docs/functions/labelComponents.mdx +20 -0
- package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
- package/api-docs/functions/playVideo.mdx +16 -0
- package/api-docs/functions/scoreComponent.mdx +22 -0
- package/api-docs/functions/setVideoElementSrc.mdx +14 -0
- package/api-docs/interfaces/Analyzer.mdx +4 -0
- package/api-docs/interfaces/Benchmark.mdx +26 -2
- package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
- package/api-docs/interfaces/Gain.mdx +4 -0
- package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
- package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
- package/api-docs/interfaces/Process.mdx +8 -3
- package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
- package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
- package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
- package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
- package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
- package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
- package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
- package/api-docs/interfaces/Renderer.mdx +2 -2
- package/api-docs/interfaces/RendererOptions.mdx +25 -12
- package/api-docs/interfaces/Segmenter.mdx +89 -10
- package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
- package/api-docs/interfaces/SelectionOptions.mdx +15 -0
- package/api-docs/interfaces/Stats.mdx +11 -0
- package/api-docs/interfaces/VideoProcessor.mdx +49 -5
- package/api-docs/interfaces/Weights.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
- package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
- package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
- package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
- package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
- package/api-docs/type-aliases/Struct.mdx +9 -0
- package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
- package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
- package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
- package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
- package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
- package/api-docs/variables/FRAME_RATE.mdx +1 -1
- package/api-docs/variables/ImageFetchError.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
- package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
- package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_PASS.mdx +3 -0
- package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
- package/api-docs/variables/PERSON_CENTER.mdx +3 -0
- package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
- package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
- package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
- package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/cloneImageRecord.mdx +19 -0
- package/api-docs/variables/compareImageRecords.mdx +18 -0
- package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
- package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
- package/api-docs/variables/getImageSize.mdx +13 -0
- package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
- package/api-docs/variables/isValidGpuAPI.mdx +13 -0
- package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
- package/dist/common/backends/webgl/binaryMask.js +95 -0
- package/dist/common/backends/webgl/blender.d.ts +2 -1
- package/dist/common/backends/webgl/blender.js +18 -15
- package/dist/common/backends/webgl/blur.d.ts +1 -1
- package/dist/common/backends/webgl/blur.js +23 -16
- package/dist/common/backends/webgl/canvas.js +9 -4
- package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
- package/dist/common/backends/webgl/diffAbsMask.js +103 -0
- package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
- package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
- package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
- package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
- package/dist/common/backends/webgl/morphErode.d.ts +13 -0
- package/dist/common/backends/webgl/morphErode.js +121 -0
- package/dist/common/backends/webgl/renderer.js +194 -44
- package/dist/common/backends/webgl/replace.d.ts +17 -0
- package/dist/common/backends/webgl/replace.js +126 -0
- package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
- package/dist/common/backends/webgl/selectComponent.js +102 -0
- package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
- package/dist/common/backends/webgl/smoothingMask.js +22 -21
- package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
- package/dist/common/backends/webgl/tentBlur.js +118 -0
- package/dist/common/backends/webgl/texture.d.ts +2 -3
- package/dist/common/backends/webgl/texture.js +21 -19
- package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgl/textureToTexture.js +19 -16
- package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
- package/dist/common/backends/webgl/webglUtils.js +72 -10
- package/dist/common/backends/webgpu/blender.d.ts +6 -1
- package/dist/common/backends/webgpu/blender.js +96 -51
- package/dist/common/backends/webgpu/constants.js +1 -1
- package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
- package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
- package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
- package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
- package/dist/common/backends/webgpu/renderer.js +205 -43
- package/dist/common/backends/webgpu/replace.d.ts +11 -0
- package/dist/common/backends/webgpu/replace.js +175 -0
- package/dist/common/backends/webgpu/resize.d.ts +4 -0
- package/dist/common/backends/webgpu/resize.js +76 -0
- package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
- package/dist/common/backends/webgpu/smoothingMask.js +74 -40
- package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
- package/dist/common/backends/webgpu/tentBlur.js +131 -0
- package/dist/common/backends/webgpu/texture.d.ts +2 -3
- package/dist/common/backends/webgpu/texture.js +29 -20
- package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgpu/textureToTexture.js +28 -15
- package/dist/common/backends/webgpu/types.d.ts +2 -0
- package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
- package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
- package/dist/common/constants.d.ts +22 -5
- package/dist/common/constants.js +23 -5
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.js +2 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/typeGuards.d.ts +2 -0
- package/dist/common/typeGuards.js +7 -0
- package/dist/common/types/media.d.ts +5 -1
- package/dist/common/types/messageEvents.d.ts +31 -4
- package/dist/common/types/processor.d.ts +6 -4
- package/dist/common/types/render.d.ts +147 -15
- package/dist/common/types/render.js +23 -1
- package/dist/common/types/segmentation.d.ts +0 -12
- package/dist/common/types/utils.d.ts +21 -0
- package/dist/common/utils.d.ts +29 -2
- package/dist/common/utils.js +206 -10
- package/dist/main/audio.js +3 -3
- package/dist/main/benchUtils.d.ts +35 -3
- package/dist/main/benchUtils.js +149 -15
- package/dist/main/index.d.ts +1 -1
- package/dist/main/index.js +1 -1
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/types.d.ts +0 -6
- package/dist/main/utils.d.ts +4 -0
- package/dist/main/utils.js +31 -0
- package/dist/main/video/constants.d.ts +1 -4
- package/dist/main/video/constants.js +1 -5
- package/dist/main/video/index.d.ts +3 -4
- package/dist/main/video/index.js +3 -4
- package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
- package/dist/main/video/inputProcessAdaptor.js +198 -0
- package/dist/main/video/segmenter.d.ts +35 -0
- package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
- package/dist/main/video/typeGuards.d.ts +2 -0
- package/dist/main/video/typeGuards.js +8 -1
- package/dist/main/video/types.d.ts +36 -16
- package/dist/main/video/utils.d.ts +5 -4
- package/dist/main/video/utils.js +49 -44
- package/dist/main/video/video.d.ts +29 -7
- package/dist/main/video/video.js +323 -28
- package/dist/workers/mediaWorker.js +35 -162
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/dist/worklets/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -12
- package/api-docs/functions/createCanvasTransform.mdx +0 -14
- package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
- package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
- package/api-docs/interfaces/Detector.mdx +0 -81
- package/api-docs/interfaces/SegmentationParams.mdx +0 -18
- package/api-docs/interfaces/Transform.mdx +0 -62
- package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
- package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
- package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
- package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
- package/dist/main/transformer.d.ts +0 -1
- package/dist/main/transformer.js +0 -4
- package/dist/main/video/canvasTransform.d.ts +0 -17
- package/dist/main/video/canvasTransform.js +0 -253
- package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
- package/dist/main/video/transformer.d.ts +0 -9
- package/dist/main/video/transformer.js +0 -68
- package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
- package/dist/main/video/videoStreamTrackProcessor.js +0 -86
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
return lazyProps.
|
|
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;
|
|
59
154
|
},
|
|
60
|
-
get
|
|
61
|
-
return lazyProps.
|
|
155
|
+
get frameDownsampler4() {
|
|
156
|
+
return lazyProps.frameDownsampler4;
|
|
157
|
+
},
|
|
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,92 @@ 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);
|
|
78
|
-
|
|
79
|
-
frame: mask,
|
|
80
|
-
width: mask.width,
|
|
81
|
-
height: mask.height,
|
|
82
|
-
timestamp: frame.timestamp,
|
|
83
|
-
});
|
|
84
|
-
const smoothedMask = props.smoothingMaskRenderer.render({
|
|
85
|
-
mask: maskTexture,
|
|
86
|
-
prevMask: props.prevMask,
|
|
87
|
-
}, options);
|
|
179
|
+
// 1. Upload current frame and raw mask
|
|
88
180
|
const frameTexture = props.videoFrameRenderer.render(frame);
|
|
89
|
-
|
|
181
|
+
const maskTexture = props.maskRenderer.render(mask);
|
|
182
|
+
// 2. Joint Bilateral Filter
|
|
183
|
+
let smoothedMask;
|
|
184
|
+
let compositeMaskInput;
|
|
185
|
+
switch (options.downSampleFactor) {
|
|
186
|
+
case 4: {
|
|
187
|
+
const maskLowRes = props.maskDownsampler4.render(maskTexture);
|
|
188
|
+
const frameLowRes = props.frameDownsampler4.render(frameTexture);
|
|
189
|
+
const filteredMaskLowRes = props.jointBilateralFilterRenderer4.render({ input: maskLowRes, guide: frameLowRes }, options);
|
|
190
|
+
smoothedMask = props.smoothingMaskRenderer4.render({
|
|
191
|
+
mask: filteredMaskLowRes,
|
|
192
|
+
prevMask: props.prevMask4 ?? filteredMaskLowRes,
|
|
193
|
+
}, options);
|
|
194
|
+
if (options.excludeBystanders) {
|
|
195
|
+
const binary = props.binaryMaskRenderer4.render(smoothedMask, options.foregroundThreshold);
|
|
196
|
+
const eroded = props.morphErodeRenderer4.render({ mask: binary }, options);
|
|
197
|
+
const selected = props.selectCenterComponent.render(eroded, options);
|
|
198
|
+
const filted = props.maskUpsampler4.render(selected);
|
|
199
|
+
smoothedMask = props.geodesicReconstructionRenderer4.render({ mask: filted, maskRef: smoothedMask }, options);
|
|
200
|
+
}
|
|
201
|
+
// Upsample to original size
|
|
202
|
+
const result = props.maskUpsampler.render(smoothedMask);
|
|
203
|
+
compositeMaskInput = props.strongMaskBlurRenderer.render(result, STRONG_EDGE_BLUR_AMOUNT);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
default: {
|
|
207
|
+
const maskLowRes = props.maskDownsampler2.render(maskTexture);
|
|
208
|
+
const frameLowRes = props.frameDownsampler2.render(frameTexture);
|
|
209
|
+
const filteredMaskLowRes = props.jointBilateralFilterRenderer2.render({ input: maskLowRes, guide: frameLowRes }, options);
|
|
210
|
+
smoothedMask = props.smoothingMaskRenderer2.render({
|
|
211
|
+
mask: filteredMaskLowRes,
|
|
212
|
+
prevMask: props.prevMask2 ?? filteredMaskLowRes,
|
|
213
|
+
}, options);
|
|
214
|
+
if (options.excludeBystanders) {
|
|
215
|
+
const binary = props.binaryMaskRenderer2.render(smoothedMask, options.foregroundThreshold);
|
|
216
|
+
const eroded = props.morphErodeRenderer.render({ mask: binary }, options);
|
|
217
|
+
const selected = props.selectCenterComponent.render(eroded, options);
|
|
218
|
+
const filted = props.maskUpsampler2.render(selected);
|
|
219
|
+
smoothedMask = props.geodesicReconstructionRenderer2.render({ mask: filted, maskRef: smoothedMask }, options);
|
|
220
|
+
}
|
|
221
|
+
// Upsample to original size
|
|
222
|
+
const result = props.maskUpsampler.render(smoothedMask);
|
|
223
|
+
compositeMaskInput = props.maskBlurRenderer.render(result, options.edgeBlurAmount);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 5. Composite final output
|
|
228
|
+
let outputFrameTexture = frameTexture;
|
|
90
229
|
if (options.effects === 'blur' && options.backgroundBlurAmount) {
|
|
91
|
-
background = props.blurRenderer.render(frameTexture, options.backgroundBlurAmount);
|
|
230
|
+
const background = props.blurRenderer.render(frameTexture, options.backgroundBlurAmount);
|
|
231
|
+
outputFrameTexture = props.blendRenderer.render({
|
|
232
|
+
mask: compositeMaskInput,
|
|
233
|
+
foreground: frameTexture,
|
|
234
|
+
background,
|
|
235
|
+
}, options);
|
|
92
236
|
}
|
|
93
237
|
else if (options?.effects === 'overlay' && options.backgroundImage) {
|
|
94
|
-
background = props.backgroundImageRenderer.render(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
238
|
+
const background = props.backgroundImageRenderer.render(options.backgroundImage);
|
|
239
|
+
outputFrameTexture = props.replaceRenderer.render({
|
|
240
|
+
mask: compositeMaskInput,
|
|
241
|
+
foreground: frameTexture,
|
|
242
|
+
background,
|
|
243
|
+
blurredBackground: props.lightWrapBlurRenderer.render(background, options.lightWrapBlurAmount),
|
|
244
|
+
timestamp: frame.timestamp,
|
|
245
|
+
}, options);
|
|
246
|
+
}
|
|
247
|
+
// 6. Output to present
|
|
248
|
+
props.canvasRenderer.render(outputFrameTexture);
|
|
249
|
+
// 7. Cloned the current smoothedMask & frame so it can be fed back to the next round
|
|
250
|
+
switch (options.downSampleFactor) {
|
|
251
|
+
case 4:
|
|
252
|
+
props.prevMask4 = props.prevMaskRenderer4.render(smoothedMask);
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
props.prevMask2 = props.prevMaskRenderer2.render(smoothedMask);
|
|
256
|
+
break;
|
|
100
257
|
}
|
|
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
258
|
};
|
|
110
259
|
const cleanup = () => {
|
|
111
260
|
props.canvasContextEventsCleanup();
|
|
112
261
|
lazyProps.release();
|
|
113
262
|
lazyProps = createLazyProps(rendererCreator);
|
|
114
|
-
props.
|
|
263
|
+
props.prevMask2 = undefined;
|
|
264
|
+
props.prevMask4 = undefined;
|
|
115
265
|
};
|
|
116
266
|
const release = () => {
|
|
117
267
|
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
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
import type { TextureInfo } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Based on Connected Component Labelling algorithm,
|
|
5
|
+
* find the center-most connected component in the mask and
|
|
6
|
+
* return a binary mask of just that component.
|
|
7
|
+
*
|
|
8
|
+
* @param processor - OffscreenCanvas for WebGL context
|
|
9
|
+
* @param width - target width of the downsampled mask to process (e.g. 32)
|
|
10
|
+
* @param height - target height of the downsampled mask to process (e.g. 18)
|
|
11
|
+
*/
|
|
12
|
+
export declare function createComponentSelector(processor: OffscreenCanvas, width: number, height: number): {
|
|
13
|
+
render: (softMask: TextureInfo, options: RendererOptions) => TextureInfo;
|
|
14
|
+
release: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { assert } from '@pexip/utils';
|
|
2
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
3
|
+
import { createTextureToTextureRenderer } from './textureToTexture';
|
|
4
|
+
import { scoreComponent, labelComponents, iou } from '../../utils';
|
|
5
|
+
/**
|
|
6
|
+
* Based on Connected Component Labelling algorithm,
|
|
7
|
+
* find the center-most connected component in the mask and
|
|
8
|
+
* return a binary mask of just that component.
|
|
9
|
+
*
|
|
10
|
+
* @param processor - OffscreenCanvas for WebGL context
|
|
11
|
+
* @param width - target width of the downsampled mask to process (e.g. 32)
|
|
12
|
+
* @param height - target height of the downsampled mask to process (e.g. 18)
|
|
13
|
+
*/
|
|
14
|
+
export function createComponentSelector(processor, width, height) {
|
|
15
|
+
const gl = processor.getContext('webgl2', {
|
|
16
|
+
premultipliedAlpha: false,
|
|
17
|
+
powerPreference: 'high-performance',
|
|
18
|
+
});
|
|
19
|
+
const downSampler = createTextureToTextureRenderer(processor, true, false, width, height, false);
|
|
20
|
+
assert(gl, RENDERING_EVENTS.ContextCreationError);
|
|
21
|
+
const labels = new Int32Array(width * height);
|
|
22
|
+
const data = new Uint8Array(width * height);
|
|
23
|
+
const bin = new Uint8Array(width * height);
|
|
24
|
+
const bestMask = new Uint8Array(width * height);
|
|
25
|
+
const labelledMask = new Uint8Array(width * height);
|
|
26
|
+
let prevPrimaryScore = 0;
|
|
27
|
+
const outputTexture = gl.createTexture();
|
|
28
|
+
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
|
|
29
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
30
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
31
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
32
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
33
|
+
const fbo = gl.createFramebuffer();
|
|
34
|
+
const config = {
|
|
35
|
+
// Frames persisted = -Math.log(1 + switchThreshold) / Math.log(decay)
|
|
36
|
+
// Width 0.3 switchThreshold and 0.95 decay, it persists about 6 frames
|
|
37
|
+
switchThreshold: 0.3,
|
|
38
|
+
decay: 0.95,
|
|
39
|
+
// Weights
|
|
40
|
+
area: 0.2,
|
|
41
|
+
height: 0.4,
|
|
42
|
+
center: 0.2,
|
|
43
|
+
iou: 0.2,
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
render: (softMask, options) => {
|
|
47
|
+
const smallMask = downSampler.render(softMask);
|
|
48
|
+
// Read back single-channel 8-bit for simplicity here
|
|
49
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
50
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, smallMask.texture, 0);
|
|
51
|
+
gl.readPixels(0, 0, width, height, gl.RED, gl.UNSIGNED_BYTE, data);
|
|
52
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
53
|
+
// Binarize using foregroundThreshold in 0..255 domain
|
|
54
|
+
const thresh = Math.max(0, Math.min(255, Math.floor(255 * options.foregroundThreshold)));
|
|
55
|
+
for (let i = 0; i < bin.length; i++) {
|
|
56
|
+
//@ts-expect-error will not be undefined
|
|
57
|
+
bin[i] = data[i] >= thresh ? 255 : 0;
|
|
58
|
+
}
|
|
59
|
+
let bestScore = -Infinity;
|
|
60
|
+
let bestLabel = 0;
|
|
61
|
+
const stats = labelComponents(bin, labels, width, height);
|
|
62
|
+
for (const [l, s] of stats.entries()) {
|
|
63
|
+
if (l === 0) {
|
|
64
|
+
continue; // background
|
|
65
|
+
}
|
|
66
|
+
for (let i = 0; i < labels.length; i++) {
|
|
67
|
+
labelledMask[i] = labels[i] === l ? 255 : 0;
|
|
68
|
+
}
|
|
69
|
+
const score = scoreComponent(s, iou(bestMask, labelledMask), {
|
|
70
|
+
...config,
|
|
71
|
+
centerX: options.personCenter[0] * width,
|
|
72
|
+
centerY: options.personCenter[1] * height,
|
|
73
|
+
}, width, height);
|
|
74
|
+
if (score > bestScore) {
|
|
75
|
+
bestScore = score;
|
|
76
|
+
bestLabel = l;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Hysteresis: switch only if significantly better
|
|
80
|
+
const shouldSwitch = bestScore > prevPrimaryScore * (1.0 + config.switchThreshold);
|
|
81
|
+
if (shouldSwitch || prevPrimaryScore === 0) {
|
|
82
|
+
for (let i = 0; i < labels.length; i++) {
|
|
83
|
+
bestMask[i] = labels[i] === bestLabel ? 255 : 0;
|
|
84
|
+
}
|
|
85
|
+
prevPrimaryScore = bestScore;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
prevPrimaryScore *= config.decay;
|
|
89
|
+
}
|
|
90
|
+
// Upload small filtered mask to GPU and return as TextureInfo (caller will upsample & combine)
|
|
91
|
+
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
|
|
92
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, bestMask);
|
|
93
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
94
|
+
return { texture: outputTexture, width, height };
|
|
95
|
+
},
|
|
96
|
+
release: () => {
|
|
97
|
+
gl.deleteTexture(outputTexture);
|
|
98
|
+
gl.deleteFramebuffer(fbo);
|
|
99
|
+
downSampler.release();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -3,9 +3,9 @@ import type { TextureInfo } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Smooth the mask edge
|
|
5
5
|
*/
|
|
6
|
-
export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas) => {
|
|
7
|
-
render: ({ mask, prevMask
|
|
8
|
-
prevMask
|
|
6
|
+
export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas, width: number, height: number) => {
|
|
7
|
+
render: ({ mask, prevMask }: {
|
|
8
|
+
prevMask: TextureInfo;
|
|
9
9
|
mask: TextureInfo;
|
|
10
10
|
}, options: RendererOptions) => TextureInfo;
|
|
11
11
|
release: () => void;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { m3 } from '../../m3';
|
|
3
3
|
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
-
import { createGLProgram,
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { createGLProgram, createFrameBuffer, createMaskTexture, } from './webglUtils';
|
|
5
|
+
import vertexSrc from './shaders/matrix.vert';
|
|
6
|
+
import fragmentSrc from './shaders/smoothingMask.frag';
|
|
7
7
|
/**
|
|
8
8
|
* Smooth the mask edge
|
|
9
9
|
*/
|
|
10
|
-
export const createSmoothingMaskRenderer = (processor) => {
|
|
11
|
-
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
10
|
+
export const createSmoothingMaskRenderer = (processor, width, height) => {
|
|
11
|
+
const { gl, program, attribLocations, uniformLocations, floatFullChannelSupported, floatSingleChannelSupported, release, } = createGLProgram(processor, {
|
|
12
12
|
attribs: {
|
|
13
13
|
position: 'a_position',
|
|
14
14
|
texCoord: 'a_texCoord',
|
|
@@ -16,12 +16,13 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
16
16
|
uniforms: {
|
|
17
17
|
matrix: 'u_matrix',
|
|
18
18
|
mask: 'u_mask',
|
|
19
|
-
maskCombineRatio: 'u_mask_combine_ratio',
|
|
20
|
-
foregroundThreshold: 'u_foreground_threshold',
|
|
21
19
|
prevMask: 'u_mask_prev',
|
|
20
|
+
frame: 'u_frame',
|
|
21
|
+
prevFrame: 'u_frame_prev',
|
|
22
|
+
maskCombineRatio: 'u_mask_combine_ratio',
|
|
22
23
|
},
|
|
23
|
-
vertexSrc
|
|
24
|
-
fragmentSrc
|
|
24
|
+
vertexSrc,
|
|
25
|
+
fragmentSrc,
|
|
25
26
|
});
|
|
26
27
|
const vao = gl.createVertexArray();
|
|
27
28
|
gl.bindVertexArray(vao);
|
|
@@ -55,16 +56,13 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
55
56
|
]), gl.STATIC_DRAW);
|
|
56
57
|
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
57
58
|
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
58
|
-
const outputTexture =
|
|
59
|
-
|
|
60
|
-
const frameBuffer = gl.createFramebuffer();
|
|
61
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
62
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
59
|
+
const outputTexture = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
|
|
60
|
+
const frameBuffer = createFrameBuffer(gl, outputTexture);
|
|
63
61
|
return {
|
|
64
|
-
render: ({ mask, prevMask
|
|
62
|
+
render: ({ mask, prevMask }, options) => {
|
|
65
63
|
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
66
64
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
67
|
-
gl.viewport(0, 0,
|
|
65
|
+
gl.viewport(0, 0, mask.width, mask.height);
|
|
68
66
|
gl.clearColor(0, 0, 0, 0);
|
|
69
67
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
70
68
|
gl.useProgram(program);
|
|
@@ -75,19 +73,22 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
75
73
|
gl.bindTexture(gl.TEXTURE_2D, mask.texture);
|
|
76
74
|
gl.activeTexture(gl.TEXTURE1);
|
|
77
75
|
gl.uniform1i(uniformLocations.prevMask, 1);
|
|
78
|
-
gl.bindTexture(gl.TEXTURE_2D, prevMask
|
|
76
|
+
gl.bindTexture(gl.TEXTURE_2D, prevMask.texture);
|
|
79
77
|
// No Flip Y Coordinate for drawing on the frame buffer
|
|
80
78
|
// Project, position and scale to full-size of the canvas
|
|
81
79
|
const matrix = m3.projection(1, 1, 1);
|
|
82
80
|
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
83
81
|
gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
|
|
84
|
-
gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
|
|
85
82
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
86
|
-
|
|
83
|
+
// Cleanup
|
|
84
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
85
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
86
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
87
|
+
gl.bindVertexArray(null);
|
|
87
88
|
return {
|
|
88
89
|
texture: outputTexture,
|
|
89
|
-
width
|
|
90
|
-
height
|
|
90
|
+
width,
|
|
91
|
+
height,
|
|
91
92
|
};
|
|
92
93
|
},
|
|
93
94
|
release: () => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TextureInfo } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a tent blur renderer for small radius blurring of a mask.
|
|
4
|
+
* The blur is implemented as a separable filter.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createTentBlurRenderer: (processor: OffscreenCanvas, width: number, height: number) => {
|
|
7
|
+
render: (mask: TextureInfo, radiusPx: number) => TextureInfo;
|
|
8
|
+
release: () => void;
|
|
9
|
+
};
|