@livekit/track-processors 0.5.3 → 0.5.5

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.
@@ -0,0 +1,108 @@
1
+ import { createProgram, createShader, glsl } from '../utils';
2
+ import { vertexShaderSource } from './vertexShader';
3
+
4
+ // Define the blur fragment shader
5
+ export const blurFragmentShader = glsl`#version 300 es
6
+ precision mediump float;
7
+ in vec2 texCoords;
8
+ uniform sampler2D u_texture;
9
+ uniform vec2 u_texelSize;
10
+ uniform vec2 u_direction;
11
+ uniform float u_radius;
12
+ out vec4 fragColor;
13
+
14
+ void main() {
15
+ float sigma = u_radius;
16
+ float twoSigmaSq = 2.0 * sigma * sigma;
17
+ float totalWeight = 0.0;
18
+ vec3 result = vec3(0.0);
19
+ const int MAX_SAMPLES = 16;
20
+ int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));
21
+
22
+ for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {
23
+ float offset = float(i);
24
+ if (abs(offset) > float(radius)) continue;
25
+ float weight = exp(-(offset * offset) / twoSigmaSq);
26
+ vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;
27
+ result += texture(u_texture, sampleCoord).rgb * weight;
28
+ totalWeight += weight;
29
+ }
30
+
31
+ fragColor = vec4(result / totalWeight, 1.0);
32
+ }
33
+ `;
34
+
35
+ export function createBlurProgram(gl: WebGL2RenderingContext) {
36
+ const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
37
+ const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);
38
+
39
+ const blurProgram = createProgram(gl, blurVertexShader, blurFrag);
40
+
41
+ // Get uniform locations
42
+ const blurUniforms = {
43
+ position: gl.getAttribLocation(blurProgram, 'position'),
44
+ texture: gl.getUniformLocation(blurProgram, 'u_texture'),
45
+ texelSize: gl.getUniformLocation(blurProgram, 'u_texelSize'),
46
+ direction: gl.getUniformLocation(blurProgram, 'u_direction'),
47
+ radius: gl.getUniformLocation(blurProgram, 'u_radius'),
48
+ };
49
+
50
+ return {
51
+ program: blurProgram,
52
+ shader: blurFrag,
53
+ vertexShader: blurVertexShader,
54
+ uniforms: blurUniforms,
55
+ };
56
+ }
57
+
58
+ export function applyBlur(
59
+ gl: WebGL2RenderingContext,
60
+ sourceTexture: WebGLTexture,
61
+ width: number,
62
+ height: number,
63
+ blurRadius: number,
64
+ blurProgram: WebGLProgram,
65
+ blurUniforms: any,
66
+ vertexBuffer: WebGLBuffer,
67
+ processFramebuffers: WebGLFramebuffer[],
68
+ processTextures: WebGLTexture[],
69
+ ) {
70
+ gl.useProgram(blurProgram);
71
+
72
+ // Set common attributes
73
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
74
+ gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);
75
+ gl.enableVertexAttribArray(blurUniforms.position);
76
+
77
+ const texelWidth = 1.0 / width;
78
+ const texelHeight = 1.0 / height;
79
+
80
+ // First pass - horizontal blur
81
+ gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);
82
+ gl.viewport(0, 0, width, height);
83
+
84
+ gl.activeTexture(gl.TEXTURE0);
85
+ gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
86
+ gl.uniform1i(blurUniforms.texture, 0);
87
+ gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);
88
+ gl.uniform2f(blurUniforms.direction, 1.0, 0.0); // Horizontal
89
+ gl.uniform1f(blurUniforms.radius, blurRadius);
90
+
91
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
92
+
93
+ // Second pass - vertical blur
94
+ gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);
95
+ gl.viewport(0, 0, width, height);
96
+
97
+ gl.activeTexture(gl.TEXTURE0);
98
+ gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);
99
+ gl.uniform1i(blurUniforms.texture, 0);
100
+ gl.uniform2f(blurUniforms.direction, 0.0, 1.0); // Vertical
101
+
102
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
103
+
104
+ // Reset framebuffer
105
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
106
+
107
+ return processTextures[1];
108
+ }
@@ -0,0 +1,60 @@
1
+ import { createProgram, createShader, glsl } from '../utils';
2
+ import { vertexShaderSource } from './vertexShader';
3
+
4
+ export const boxBlurFragmentShader = glsl`#version 300 es
5
+ precision mediump float;
6
+
7
+ in vec2 texCoords;
8
+
9
+ uniform sampler2D u_texture;
10
+ uniform vec2 u_texelSize; // 1.0 / texture size
11
+ uniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical
12
+ uniform float u_radius; // blur radius in texels
13
+
14
+ out vec4 fragColor;
15
+
16
+ void main() {
17
+ vec3 sum = vec3(0.0);
18
+ float count = 0.0;
19
+
20
+ // Limit radius to avoid excessive loop cost
21
+ const int MAX_RADIUS = 16;
22
+ int radius = int(min(float(MAX_RADIUS), u_radius));
23
+
24
+ for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {
25
+ if (abs(i) > radius) continue;
26
+
27
+ vec2 offset = u_direction * u_texelSize * float(i);
28
+ sum += texture(u_texture, texCoords + offset).rgb;
29
+ count += 1.0;
30
+ }
31
+
32
+ fragColor = vec4(sum / count, 1.0);
33
+ }
34
+ `;
35
+
36
+ /**
37
+ * Create the box blur shader program
38
+ */
39
+ export function createBoxBlurProgram(gl: WebGL2RenderingContext) {
40
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
41
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);
42
+
43
+ const program = createProgram(gl, vertexShader, fragmentShader);
44
+
45
+ // Get attribute and uniform locations
46
+ const uniforms = {
47
+ position: gl.getAttribLocation(program, 'position'),
48
+ texture: gl.getUniformLocation(program, 'u_texture'),
49
+ texelSize: gl.getUniformLocation(program, 'u_texelSize'),
50
+ direction: gl.getUniformLocation(program, 'u_direction'),
51
+ radius: gl.getUniformLocation(program, 'u_radius'),
52
+ };
53
+
54
+ return {
55
+ program,
56
+ vertexShader,
57
+ fragmentShader,
58
+ uniforms,
59
+ };
60
+ }
@@ -0,0 +1,64 @@
1
+ import { createProgram, createShader, glsl } from '../utils';
2
+ import { vertexShaderSource } from './vertexShader';
3
+
4
+ // Fragment shader source for compositing
5
+ export const compositeFragmentShader = glsl`#version 300 es
6
+ precision mediump float;
7
+ in vec2 texCoords;
8
+ uniform sampler2D background;
9
+ uniform sampler2D frame;
10
+ uniform sampler2D mask;
11
+ out vec4 fragColor;
12
+
13
+ void main() {
14
+
15
+ vec4 frameTex = texture(frame, texCoords);
16
+ vec4 bgTex = texture(background, texCoords);
17
+
18
+ float maskVal = texture(mask, texCoords).r;
19
+
20
+ // Compute screen-space gradient to detect edge sharpness
21
+ float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));
22
+
23
+ float edgeSoftness = 2.0; // higher = softer
24
+
25
+ // Create a smooth edge around binary transition
26
+ float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);
27
+
28
+ // Optional: preserve frame alpha, or override as fully opaque
29
+ vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);
30
+
31
+ fragColor = blended;
32
+
33
+ }
34
+ `;
35
+
36
+ /**
37
+ * Create the composite shader program
38
+ */
39
+ export function createCompositeProgram(gl: WebGL2RenderingContext) {
40
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
41
+ const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);
42
+
43
+ const compositeProgram = createProgram(gl, vertexShader, compositeShader);
44
+
45
+ // Get attribute and uniform locations
46
+ const attribLocations = {
47
+ position: gl.getAttribLocation(compositeProgram, 'position'),
48
+ };
49
+
50
+ const uniformLocations = {
51
+ mask: gl.getUniformLocation(compositeProgram, 'mask')!,
52
+ frame: gl.getUniformLocation(compositeProgram, 'frame')!,
53
+ background: gl.getUniformLocation(compositeProgram, 'background')!,
54
+ stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,
55
+ };
56
+
57
+ return {
58
+ program: compositeProgram,
59
+ vertexShader,
60
+ fragmentShader: compositeShader,
61
+ attribLocations,
62
+ uniformLocations,
63
+ };
64
+ }
@@ -0,0 +1,94 @@
1
+ import { createProgram, createShader } from '../utils';
2
+
3
+ export function createDownSampler(
4
+ gl: WebGL2RenderingContext,
5
+ width: number,
6
+ height: number,
7
+ ): {
8
+ framebuffer: WebGLFramebuffer;
9
+ texture: WebGLTexture;
10
+ program: WebGLProgram;
11
+ uniforms: any;
12
+ } {
13
+ // Create texture
14
+ const texture = gl.createTexture()!;
15
+ gl.bindTexture(gl.TEXTURE_2D, texture);
16
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
17
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
18
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
19
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
20
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
21
+
22
+ // Create framebuffer
23
+ const framebuffer = gl.createFramebuffer()!;
24
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
25
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
26
+
27
+ // Create shader program for copying
28
+ const vertexSource = `
29
+ attribute vec2 position;
30
+ varying vec2 v_uv;
31
+ void main() {
32
+ v_uv = (position + 1.0) * 0.5;
33
+ gl_Position = vec4(position, 0.0, 1.0);
34
+ }
35
+ `;
36
+
37
+ const fragmentSource = `
38
+ precision mediump float;
39
+ varying vec2 v_uv;
40
+ uniform sampler2D u_texture;
41
+ void main() {
42
+ gl_FragColor = texture2D(u_texture, v_uv);
43
+ }
44
+ `;
45
+
46
+ const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
47
+ const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
48
+ const program = createProgram(gl, vertShader, fragShader);
49
+
50
+ const uniforms = {
51
+ texture: gl.getUniformLocation(program, 'u_texture'),
52
+ position: gl.getAttribLocation(program, 'position'),
53
+ };
54
+
55
+ return {
56
+ framebuffer,
57
+ texture,
58
+ program,
59
+ uniforms,
60
+ };
61
+ }
62
+
63
+ export function applyDownsampling(
64
+ gl: WebGL2RenderingContext,
65
+ inputTexture: WebGLTexture,
66
+ downSampler: {
67
+ framebuffer: WebGLFramebuffer;
68
+ texture: WebGLTexture;
69
+ program: WebGLProgram;
70
+ uniforms: any;
71
+ },
72
+ vertexBuffer: WebGLBuffer,
73
+ width: number,
74
+ height: number,
75
+ ): WebGLTexture {
76
+ gl.useProgram(downSampler.program);
77
+
78
+ gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);
79
+ gl.viewport(0, 0, width, height);
80
+
81
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
82
+ gl.enableVertexAttribArray(downSampler.uniforms.position);
83
+ gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);
84
+
85
+ gl.activeTexture(gl.TEXTURE0);
86
+ gl.bindTexture(gl.TEXTURE_2D, inputTexture);
87
+ gl.uniform1i(downSampler.uniforms.texture, 0);
88
+
89
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
90
+
91
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
92
+
93
+ return downSampler.texture;
94
+ }
@@ -0,0 +1,11 @@
1
+ // Vertex shader source
2
+ export const vertexShaderSource = (flipY: boolean = true) => `#version 300 es
3
+ in vec2 position;
4
+ out vec2 texCoords;
5
+
6
+ void main() {
7
+ texCoords = (position + 1.0) / 2.0;
8
+ texCoords.y = ${flipY ? '1.0 - texCoords.y' : 'texCoords.y'};
9
+ gl_Position = vec4(position, 0, 1.0);
10
+ }
11
+ `;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Initialize a WebGL texture
3
+ */
4
+ export function initTexture(gl: WebGL2RenderingContext, texIndex: number) {
5
+ const texRef = gl.TEXTURE0 + texIndex;
6
+ gl.activeTexture(texRef);
7
+ const texture = gl.createTexture();
8
+ gl.bindTexture(gl.TEXTURE_2D, texture);
9
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
10
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
11
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
12
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
13
+ gl.bindTexture(gl.TEXTURE_2D, texture);
14
+
15
+ return texture;
16
+ }
17
+
18
+ export function createShader(
19
+ gl: WebGL2RenderingContext,
20
+ type: number,
21
+ source: string,
22
+ ): WebGLShader {
23
+ const shader = gl.createShader(type)!;
24
+ gl.shaderSource(shader, source);
25
+ gl.compileShader(shader);
26
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
27
+ console.error('Shader compile failed:', gl.getShaderInfoLog(shader));
28
+ gl.deleteShader(shader);
29
+ throw new Error('Shader compile failed');
30
+ }
31
+ return shader;
32
+ }
33
+
34
+ export function createProgram(
35
+ gl: WebGL2RenderingContext,
36
+ vs: WebGLShader,
37
+ fs: WebGLShader,
38
+ ): WebGLProgram {
39
+ const program = gl.createProgram()!;
40
+ gl.attachShader(program, vs);
41
+ gl.attachShader(program, fs);
42
+ gl.linkProgram(program);
43
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
44
+ console.error('Program link failed:', gl.getProgramInfoLog(program));
45
+ throw new Error('Program link failed');
46
+ }
47
+ return program;
48
+ }
49
+
50
+ /**
51
+ * Create a WebGL framebuffer with the given texture as color attachment
52
+ */
53
+ export function createFramebuffer(
54
+ gl: WebGL2RenderingContext,
55
+ texture: WebGLTexture,
56
+ width: number,
57
+ height: number,
58
+ ) {
59
+ const framebuffer = gl.createFramebuffer();
60
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
61
+
62
+ // Set the texture as the color attachment
63
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
64
+
65
+ // Ensure texture dimensions match the provided width and height
66
+ gl.bindTexture(gl.TEXTURE_2D, texture);
67
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
68
+
69
+ // Check if framebuffer is complete
70
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
71
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
72
+ throw new Error('Framebuffer not complete');
73
+ }
74
+
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
76
+ return framebuffer;
77
+ }
78
+
79
+ /**
80
+ * Create a vertex buffer for a full-screen quad
81
+ */
82
+ export function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {
83
+ const vertexBuffer = gl.createBuffer();
84
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
85
+ gl.bufferData(
86
+ gl.ARRAY_BUFFER,
87
+ new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
88
+ gl.STATIC_DRAW,
89
+ );
90
+ return vertexBuffer;
91
+ }
92
+
93
+ /**
94
+ * Resizes and crops an image to cover a target canvas while maintaining aspect ratio
95
+ * @param image The source image
96
+ * @param targetWidth The target width
97
+ * @param targetHeight The target height
98
+ * @returns A cropped and resized ImageBitmap
99
+ */
100
+ export async function resizeImageToCover(
101
+ image: ImageBitmap,
102
+ targetWidth: number,
103
+ targetHeight: number,
104
+ ): Promise<ImageBitmap> {
105
+ // Calculate dimensions and crop for "cover" mode
106
+ const imgAspect = image.width / image.height;
107
+ const targetAspect = targetWidth / targetHeight;
108
+
109
+ let sx = 0;
110
+ let sy = 0;
111
+ let sWidth = image.width;
112
+ let sHeight = image.height;
113
+
114
+ // For cover mode, we need to crop some parts of the image
115
+ // to ensure it covers the canvas while maintaining aspect ratio
116
+ if (imgAspect > targetAspect) {
117
+ // Image is wider than target - crop the sides
118
+ sWidth = Math.round(image.height * targetAspect);
119
+ sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally
120
+ } else if (imgAspect < targetAspect) {
121
+ // Image is taller than target - crop the top/bottom
122
+ sHeight = Math.round(image.width / targetAspect);
123
+ sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically
124
+ }
125
+
126
+ // Create a new ImageBitmap with the cropped portion
127
+ return createImageBitmap(image, sx, sy, sWidth, sHeight, {
128
+ resizeWidth: targetWidth,
129
+ resizeHeight: targetHeight,
130
+ resizeQuality: 'medium',
131
+ });
132
+ }
133
+
134
+ const emptyImageData = new ImageData(2, 2);
135
+ emptyImageData.data[0] = 0;
136
+ emptyImageData.data[1] = 0;
137
+ emptyImageData.data[2] = 0;
138
+ emptyImageData.data[3] = 0;
139
+
140
+ const glsl = (source: any) => source;
141
+
142
+ export { emptyImageData, glsl };