@livekit/track-processors 0.5.3 → 0.5.4

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,98 @@
1
+ import { 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
+ // Create vertex shader
41
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
42
+ if (!vertexShader) {
43
+ throw Error('cannot create vertex shader');
44
+ }
45
+ gl.shaderSource(vertexShader, vertexShaderSource());
46
+ gl.compileShader(vertexShader);
47
+
48
+ // Check vertex shader compilation
49
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
50
+ const info = gl.getShaderInfoLog(vertexShader);
51
+ throw Error(`Failed to compile vertex shader: ${info}`);
52
+ }
53
+
54
+ // Create fragment shader
55
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
56
+ if (!fragmentShader) {
57
+ throw Error('cannot create fragment shader');
58
+ }
59
+ gl.shaderSource(fragmentShader, boxBlurFragmentShader);
60
+ gl.compileShader(fragmentShader);
61
+
62
+ // Check fragment shader compilation
63
+ if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
64
+ const info = gl.getShaderInfoLog(fragmentShader);
65
+ throw Error(`Failed to compile box blur shader: ${info}`);
66
+ }
67
+
68
+ // Create the program
69
+ const program = gl.createProgram();
70
+ if (!program) {
71
+ throw Error('cannot create box blur program');
72
+ }
73
+ gl.attachShader(program, vertexShader);
74
+ gl.attachShader(program, fragmentShader);
75
+ gl.linkProgram(program);
76
+
77
+ // Check program link status
78
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
79
+ const info = gl.getProgramInfoLog(program);
80
+ throw Error(`Failed to link box blur program: ${info}`);
81
+ }
82
+
83
+ // Get attribute and uniform locations
84
+ const uniforms = {
85
+ position: gl.getAttribLocation(program, 'position'),
86
+ texture: gl.getUniformLocation(program, 'u_texture'),
87
+ texelSize: gl.getUniformLocation(program, 'u_texelSize'),
88
+ direction: gl.getUniformLocation(program, 'u_direction'),
89
+ radius: gl.getUniformLocation(program, 'u_radius'),
90
+ };
91
+
92
+ return {
93
+ program,
94
+ vertexShader,
95
+ fragmentShader,
96
+ uniforms,
97
+ };
98
+ }
@@ -0,0 +1,102 @@
1
+ import { 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 highp 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
+ // Create vertex shader
41
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
42
+ if (!vertexShader) {
43
+ throw Error('cannot create vertex shader');
44
+ }
45
+ gl.shaderSource(vertexShader, vertexShaderSource());
46
+ gl.compileShader(vertexShader);
47
+
48
+ // Check vertex shader compilation
49
+ if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
50
+ const info = gl.getShaderInfoLog(vertexShader);
51
+ throw Error(`Failed to compile vertex shader: ${info}`);
52
+ }
53
+
54
+ // Create fragment shader
55
+ const compositeShader = gl.createShader(gl.FRAGMENT_SHADER);
56
+ if (!compositeShader) {
57
+ throw Error('cannot create fragment shader');
58
+ }
59
+ gl.shaderSource(compositeShader, compositeFragmentShader);
60
+ gl.compileShader(compositeShader);
61
+
62
+ // Check fragment shader compilation
63
+ if (!gl.getShaderParameter(compositeShader, gl.COMPILE_STATUS)) {
64
+ const info = gl.getShaderInfoLog(compositeShader);
65
+ throw Error(`Failed to compile composite shader: ${info}`);
66
+ }
67
+
68
+ // Create the program
69
+ const compositeProgram = gl.createProgram();
70
+ if (!compositeProgram) {
71
+ throw Error('cannot create composite program');
72
+ }
73
+ gl.attachShader(compositeProgram, vertexShader);
74
+ gl.attachShader(compositeProgram, compositeShader);
75
+ gl.linkProgram(compositeProgram);
76
+
77
+ // Check program link status
78
+ if (!gl.getProgramParameter(compositeProgram, gl.LINK_STATUS)) {
79
+ const info = gl.getProgramInfoLog(compositeProgram);
80
+ throw Error(`Failed to link composite program: ${info}`);
81
+ }
82
+
83
+ // Get attribute and uniform locations
84
+ const attribLocations = {
85
+ position: gl.getAttribLocation(compositeProgram, 'position'),
86
+ };
87
+
88
+ const uniformLocations = {
89
+ mask: gl.getUniformLocation(compositeProgram, 'mask')!,
90
+ frame: gl.getUniformLocation(compositeProgram, 'frame')!,
91
+ background: gl.getUniformLocation(compositeProgram, 'background')!,
92
+ stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,
93
+ };
94
+
95
+ return {
96
+ program: compositeProgram,
97
+ vertexShader,
98
+ fragmentShader: compositeShader,
99
+ attribLocations,
100
+ uniformLocations,
101
+ };
102
+ }
@@ -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,110 @@
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
+ /**
19
+ * Create a WebGL framebuffer with the given texture as color attachment
20
+ */
21
+ export function createFramebuffer(
22
+ gl: WebGL2RenderingContext,
23
+ texture: WebGLTexture,
24
+ width: number,
25
+ height: number,
26
+ ) {
27
+ const framebuffer = gl.createFramebuffer();
28
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
29
+
30
+ // Set the texture as the color attachment
31
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
32
+
33
+ // Ensure texture dimensions match the provided width and height
34
+ gl.bindTexture(gl.TEXTURE_2D, texture);
35
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
36
+
37
+ // Check if framebuffer is complete
38
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
39
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
40
+ throw new Error('Framebuffer not complete');
41
+ }
42
+
43
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
44
+ return framebuffer;
45
+ }
46
+
47
+ /**
48
+ * Create a vertex buffer for a full-screen quad
49
+ */
50
+ export function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {
51
+ const vertexBuffer = gl.createBuffer();
52
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
53
+ gl.bufferData(
54
+ gl.ARRAY_BUFFER,
55
+ new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
56
+ gl.STATIC_DRAW,
57
+ );
58
+ return vertexBuffer;
59
+ }
60
+
61
+ /**
62
+ * Resizes and crops an image to cover a target canvas while maintaining aspect ratio
63
+ * @param image The source image
64
+ * @param targetWidth The target width
65
+ * @param targetHeight The target height
66
+ * @returns A cropped and resized ImageBitmap
67
+ */
68
+ export async function resizeImageToCover(
69
+ image: ImageBitmap,
70
+ targetWidth: number,
71
+ targetHeight: number,
72
+ ): Promise<ImageBitmap> {
73
+ // Calculate dimensions and crop for "cover" mode
74
+ const imgAspect = image.width / image.height;
75
+ const targetAspect = targetWidth / targetHeight;
76
+
77
+ let sx = 0;
78
+ let sy = 0;
79
+ let sWidth = image.width;
80
+ let sHeight = image.height;
81
+
82
+ // For cover mode, we need to crop some parts of the image
83
+ // to ensure it covers the canvas while maintaining aspect ratio
84
+ if (imgAspect > targetAspect) {
85
+ // Image is wider than target - crop the sides
86
+ sWidth = Math.round(image.height * targetAspect);
87
+ sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally
88
+ } else if (imgAspect < targetAspect) {
89
+ // Image is taller than target - crop the top/bottom
90
+ sHeight = Math.round(image.width / targetAspect);
91
+ sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically
92
+ }
93
+
94
+ // Create a new ImageBitmap with the cropped portion
95
+ return createImageBitmap(image, sx, sy, sWidth, sHeight, {
96
+ resizeWidth: targetWidth,
97
+ resizeHeight: targetHeight,
98
+ resizeQuality: 'medium',
99
+ });
100
+ }
101
+
102
+ const emptyImageData = new ImageData(2, 2);
103
+ emptyImageData.data[0] = 0;
104
+ emptyImageData.data[1] = 0;
105
+ emptyImageData.data[2] = 0;
106
+ emptyImageData.data[3] = 0;
107
+
108
+ const glsl = (source: any) => source;
109
+
110
+ export { emptyImageData, glsl };