@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.
package/dist/index.js CHANGED
@@ -251,14 +251,112 @@ var dependencies = {
251
251
  "@mediapipe/tasks-vision": "0.10.14"
252
252
  };
253
253
 
254
- // src/webgl/index.ts
255
- var blurFragmentShader = `
256
- precision highp float;
257
- varying vec2 texCoords;
254
+ // src/webgl/utils.ts
255
+ function initTexture(gl, texIndex) {
256
+ const texRef = gl.TEXTURE0 + texIndex;
257
+ gl.activeTexture(texRef);
258
+ const texture = gl.createTexture();
259
+ gl.bindTexture(gl.TEXTURE_2D, texture);
260
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
261
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
262
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
263
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
264
+ gl.bindTexture(gl.TEXTURE_2D, texture);
265
+ return texture;
266
+ }
267
+ function createShader(gl, type, source) {
268
+ const shader = gl.createShader(type);
269
+ gl.shaderSource(shader, source);
270
+ gl.compileShader(shader);
271
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
272
+ console.error("Shader compile failed:", gl.getShaderInfoLog(shader));
273
+ gl.deleteShader(shader);
274
+ throw new Error("Shader compile failed");
275
+ }
276
+ return shader;
277
+ }
278
+ function createProgram(gl, vs, fs) {
279
+ const program = gl.createProgram();
280
+ gl.attachShader(program, vs);
281
+ gl.attachShader(program, fs);
282
+ gl.linkProgram(program);
283
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
284
+ console.error("Program link failed:", gl.getProgramInfoLog(program));
285
+ throw new Error("Program link failed");
286
+ }
287
+ return program;
288
+ }
289
+ function createFramebuffer(gl, texture, width, height) {
290
+ const framebuffer = gl.createFramebuffer();
291
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
292
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
293
+ gl.bindTexture(gl.TEXTURE_2D, texture);
294
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
295
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
296
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
297
+ throw new Error("Framebuffer not complete");
298
+ }
299
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
300
+ return framebuffer;
301
+ }
302
+ function createVertexBuffer(gl) {
303
+ const vertexBuffer = gl.createBuffer();
304
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
305
+ gl.bufferData(
306
+ gl.ARRAY_BUFFER,
307
+ new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
308
+ gl.STATIC_DRAW
309
+ );
310
+ return vertexBuffer;
311
+ }
312
+ async function resizeImageToCover(image, targetWidth, targetHeight) {
313
+ const imgAspect = image.width / image.height;
314
+ const targetAspect = targetWidth / targetHeight;
315
+ let sx = 0;
316
+ let sy = 0;
317
+ let sWidth = image.width;
318
+ let sHeight = image.height;
319
+ if (imgAspect > targetAspect) {
320
+ sWidth = Math.round(image.height * targetAspect);
321
+ sx = Math.round((image.width - sWidth) / 2);
322
+ } else if (imgAspect < targetAspect) {
323
+ sHeight = Math.round(image.width / targetAspect);
324
+ sy = Math.round((image.height - sHeight) / 2);
325
+ }
326
+ return createImageBitmap(image, sx, sy, sWidth, sHeight, {
327
+ resizeWidth: targetWidth,
328
+ resizeHeight: targetHeight,
329
+ resizeQuality: "medium"
330
+ });
331
+ }
332
+ var emptyImageData = new ImageData(2, 2);
333
+ emptyImageData.data[0] = 0;
334
+ emptyImageData.data[1] = 0;
335
+ emptyImageData.data[2] = 0;
336
+ emptyImageData.data[3] = 0;
337
+ var glsl = (source) => source;
338
+
339
+ // src/webgl/shader-programs/vertexShader.ts
340
+ var vertexShaderSource = (flipY = true) => `#version 300 es
341
+ in vec2 position;
342
+ out vec2 texCoords;
343
+
344
+ void main() {
345
+ texCoords = (position + 1.0) / 2.0;
346
+ texCoords.y = ${flipY ? "1.0 - texCoords.y" : "texCoords.y"};
347
+ gl_Position = vec4(position, 0, 1.0);
348
+ }
349
+ `;
350
+
351
+ // src/webgl/shader-programs/blurShader.ts
352
+ var blurFragmentShader = glsl`#version 300 es
353
+ precision mediump float;
354
+ in vec2 texCoords;
258
355
  uniform sampler2D u_texture;
259
356
  uniform vec2 u_texelSize;
260
357
  uniform vec2 u_direction;
261
358
  uniform float u_radius;
359
+ out vec4 fragColor;
262
360
 
263
361
  void main() {
264
362
  float sigma = u_radius;
@@ -273,93 +371,18 @@ var blurFragmentShader = `
273
371
  if (abs(offset) > float(radius)) continue;
274
372
  float weight = exp(-(offset * offset) / twoSigmaSq);
275
373
  vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;
276
- result += texture2D(u_texture, sampleCoord).rgb * weight;
374
+ result += texture(u_texture, sampleCoord).rgb * weight;
277
375
  totalWeight += weight;
278
376
  }
279
377
 
280
- gl_FragColor = vec4(result / totalWeight, 1.0);
378
+ fragColor = vec4(result / totalWeight, 1.0);
281
379
  }
282
380
  `;
283
- var createShaderProgram = (gl) => {
284
- const vs = `
285
- attribute vec2 position;
286
- varying vec2 texCoords;
287
-
288
- void main() {
289
- texCoords = (position + 1.0) / 2.0;
290
- texCoords.y = 1.0 - texCoords.y;
291
- gl_Position = vec4(position, 0, 1.0);
292
- }
293
- `;
294
- const cS = `
295
- precision highp float;
296
- varying vec2 texCoords;
297
- uniform sampler2D background;
298
- uniform sampler2D frame;
299
- uniform sampler2D mask;
300
- void main() {
301
- vec4 maskTex = texture2D(mask, texCoords);
302
- vec4 frameTex = texture2D(frame, texCoords);
303
- vec4 bgTex = texture2D(background, texCoords);
304
-
305
-
306
- float a = maskTex.r;
307
-
308
- gl_FragColor = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - a);
309
-
310
- }
311
- `;
312
- const vertexShader = gl.createShader(gl.VERTEX_SHADER);
313
- if (!vertexShader) {
314
- throw Error("can not create vertex shader");
315
- }
316
- gl.shaderSource(vertexShader, vs);
317
- gl.compileShader(vertexShader);
318
- const compositeShader = gl.createShader(gl.FRAGMENT_SHADER);
319
- if (!compositeShader) {
320
- throw Error("can not create fragment shader");
321
- }
322
- gl.shaderSource(compositeShader, cS);
323
- gl.compileShader(compositeShader);
324
- const compositeProgram = gl.createProgram();
325
- if (!compositeProgram) {
326
- throw Error("can not create composite program");
327
- }
328
- gl.attachShader(compositeProgram, vertexShader);
329
- gl.attachShader(compositeProgram, compositeShader);
330
- gl.linkProgram(compositeProgram);
331
- let blurProgram = null;
332
- let blurVertexShader = null;
333
- let blurFrag = null;
334
- let blurUniforms = null;
335
- blurFrag = gl.createShader(gl.FRAGMENT_SHADER);
336
- if (!blurFrag) {
337
- throw Error("can not create blur shader");
338
- }
339
- gl.shaderSource(blurFrag, blurFragmentShader);
340
- gl.compileShader(blurFrag);
341
- if (!gl.getShaderParameter(blurFrag, gl.COMPILE_STATUS)) {
342
- const info = gl.getShaderInfoLog(blurFrag);
343
- throw Error(`Failed to compile blur shader: ${info}`);
344
- }
345
- blurVertexShader = gl.createShader(gl.VERTEX_SHADER);
346
- if (!blurVertexShader) {
347
- throw Error("can not create blur vertex shader");
348
- }
349
- gl.shaderSource(blurVertexShader, vs);
350
- gl.compileShader(blurVertexShader);
351
- blurProgram = gl.createProgram();
352
- if (!blurProgram) {
353
- throw Error("can not create blur program");
354
- }
355
- gl.attachShader(blurProgram, blurVertexShader);
356
- gl.attachShader(blurProgram, blurFrag);
357
- gl.linkProgram(blurProgram);
358
- if (!gl.getProgramParameter(blurProgram, gl.LINK_STATUS)) {
359
- const info = gl.getProgramInfoLog(blurProgram);
360
- throw Error(`Failed to link blur program: ${info}`);
361
- }
362
- blurUniforms = {
381
+ function createBlurProgram(gl) {
382
+ const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
383
+ const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);
384
+ const blurProgram = createProgram(gl, blurVertexShader, blurFrag);
385
+ const blurUniforms = {
363
386
  position: gl.getAttribLocation(blurProgram, "position"),
364
387
  texture: gl.getUniformLocation(blurProgram, "u_texture"),
365
388
  texelSize: gl.getUniformLocation(blurProgram, "u_texelSize"),
@@ -367,123 +390,267 @@ var createShaderProgram = (gl) => {
367
390
  radius: gl.getUniformLocation(blurProgram, "u_radius")
368
391
  };
369
392
  return {
393
+ program: blurProgram,
394
+ shader: blurFrag,
395
+ vertexShader: blurVertexShader,
396
+ uniforms: blurUniforms
397
+ };
398
+ }
399
+ function applyBlur(gl, sourceTexture, width, height, blurRadius, blurProgram, blurUniforms, vertexBuffer, processFramebuffers, processTextures) {
400
+ gl.useProgram(blurProgram);
401
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
402
+ gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);
403
+ gl.enableVertexAttribArray(blurUniforms.position);
404
+ const texelWidth = 1 / width;
405
+ const texelHeight = 1 / height;
406
+ gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);
407
+ gl.viewport(0, 0, width, height);
408
+ gl.activeTexture(gl.TEXTURE0);
409
+ gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
410
+ gl.uniform1i(blurUniforms.texture, 0);
411
+ gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);
412
+ gl.uniform2f(blurUniforms.direction, 1, 0);
413
+ gl.uniform1f(blurUniforms.radius, blurRadius);
414
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
415
+ gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);
416
+ gl.viewport(0, 0, width, height);
417
+ gl.activeTexture(gl.TEXTURE0);
418
+ gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);
419
+ gl.uniform1i(blurUniforms.texture, 0);
420
+ gl.uniform2f(blurUniforms.direction, 0, 1);
421
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
422
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
423
+ return processTextures[1];
424
+ }
425
+
426
+ // src/webgl/shader-programs/boxBlurShader.ts
427
+ var boxBlurFragmentShader = glsl`#version 300 es
428
+ precision mediump float;
429
+
430
+ in vec2 texCoords;
431
+
432
+ uniform sampler2D u_texture;
433
+ uniform vec2 u_texelSize; // 1.0 / texture size
434
+ uniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical
435
+ uniform float u_radius; // blur radius in texels
436
+
437
+ out vec4 fragColor;
438
+
439
+ void main() {
440
+ vec3 sum = vec3(0.0);
441
+ float count = 0.0;
442
+
443
+ // Limit radius to avoid excessive loop cost
444
+ const int MAX_RADIUS = 16;
445
+ int radius = int(min(float(MAX_RADIUS), u_radius));
446
+
447
+ for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {
448
+ if (abs(i) > radius) continue;
449
+
450
+ vec2 offset = u_direction * u_texelSize * float(i);
451
+ sum += texture(u_texture, texCoords + offset).rgb;
452
+ count += 1.0;
453
+ }
454
+
455
+ fragColor = vec4(sum / count, 1.0);
456
+ }
457
+ `;
458
+ function createBoxBlurProgram(gl) {
459
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
460
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);
461
+ const program = createProgram(gl, vertexShader, fragmentShader);
462
+ const uniforms = {
463
+ position: gl.getAttribLocation(program, "position"),
464
+ texture: gl.getUniformLocation(program, "u_texture"),
465
+ texelSize: gl.getUniformLocation(program, "u_texelSize"),
466
+ direction: gl.getUniformLocation(program, "u_direction"),
467
+ radius: gl.getUniformLocation(program, "u_radius")
468
+ };
469
+ return {
470
+ program,
370
471
  vertexShader,
371
- compositeShader,
372
- blurShader: blurFrag,
373
- compositeProgram,
374
- blurProgram,
375
- attribLocations: {
376
- position: gl.getAttribLocation(compositeProgram, "position")
377
- },
378
- uniformLocations: {
379
- mask: gl.getUniformLocation(compositeProgram, "mask"),
380
- frame: gl.getUniformLocation(compositeProgram, "frame"),
381
- background: gl.getUniformLocation(compositeProgram, "background")
382
- },
383
- blurUniforms
472
+ fragmentShader,
473
+ uniforms
384
474
  };
385
- };
386
- function initTexture(gl, texIndex) {
387
- const texRef = gl.TEXTURE0 + texIndex;
388
- gl.activeTexture(texRef);
475
+ }
476
+
477
+ // src/webgl/shader-programs/compositeShader.ts
478
+ var compositeFragmentShader = glsl`#version 300 es
479
+ precision mediump float;
480
+ in vec2 texCoords;
481
+ uniform sampler2D background;
482
+ uniform sampler2D frame;
483
+ uniform sampler2D mask;
484
+ out vec4 fragColor;
485
+
486
+ void main() {
487
+
488
+ vec4 frameTex = texture(frame, texCoords);
489
+ vec4 bgTex = texture(background, texCoords);
490
+
491
+ float maskVal = texture(mask, texCoords).r;
492
+
493
+ // Compute screen-space gradient to detect edge sharpness
494
+ float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));
495
+
496
+ float edgeSoftness = 2.0; // higher = softer
497
+
498
+ // Create a smooth edge around binary transition
499
+ float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);
500
+
501
+ // Optional: preserve frame alpha, or override as fully opaque
502
+ vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);
503
+
504
+ fragColor = blended;
505
+
506
+ }
507
+ `;
508
+ function createCompositeProgram(gl) {
509
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
510
+ const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);
511
+ const compositeProgram = createProgram(gl, vertexShader, compositeShader);
512
+ const attribLocations = {
513
+ position: gl.getAttribLocation(compositeProgram, "position")
514
+ };
515
+ const uniformLocations = {
516
+ mask: gl.getUniformLocation(compositeProgram, "mask"),
517
+ frame: gl.getUniformLocation(compositeProgram, "frame"),
518
+ background: gl.getUniformLocation(compositeProgram, "background"),
519
+ stepWidth: gl.getUniformLocation(compositeProgram, "u_stepWidth")
520
+ };
521
+ return {
522
+ program: compositeProgram,
523
+ vertexShader,
524
+ fragmentShader: compositeShader,
525
+ attribLocations,
526
+ uniformLocations
527
+ };
528
+ }
529
+
530
+ // src/webgl/shader-programs/downSampler.ts
531
+ function createDownSampler(gl, width, height) {
389
532
  const texture = gl.createTexture();
390
533
  gl.bindTexture(gl.TEXTURE_2D, texture);
391
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
392
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
534
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
393
535
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
394
536
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
395
- gl.bindTexture(gl.TEXTURE_2D, texture);
396
- return texture;
397
- }
398
- function createFramebuffer(gl, texture, width, height) {
537
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
538
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
399
539
  const framebuffer = gl.createFramebuffer();
400
540
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
401
541
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
402
- gl.bindTexture(gl.TEXTURE_2D, texture);
403
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
404
- const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
405
- if (status !== gl.FRAMEBUFFER_COMPLETE) {
406
- throw new Error("Framebuffer not complete");
407
- }
408
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
409
- return framebuffer;
542
+ const vertexSource = `
543
+ attribute vec2 position;
544
+ varying vec2 v_uv;
545
+ void main() {
546
+ v_uv = (position + 1.0) * 0.5;
547
+ gl_Position = vec4(position, 0.0, 1.0);
548
+ }
549
+ `;
550
+ const fragmentSource = `
551
+ precision mediump float;
552
+ varying vec2 v_uv;
553
+ uniform sampler2D u_texture;
554
+ void main() {
555
+ gl_FragColor = texture2D(u_texture, v_uv);
556
+ }
557
+ `;
558
+ const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
559
+ const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
560
+ const program = createProgram(gl, vertShader, fragShader);
561
+ const uniforms = {
562
+ texture: gl.getUniformLocation(program, "u_texture"),
563
+ position: gl.getAttribLocation(program, "position")
564
+ };
565
+ return {
566
+ framebuffer,
567
+ texture,
568
+ program,
569
+ uniforms
570
+ };
410
571
  }
411
- var createVertexBuffer = (gl) => {
412
- if (!gl) {
413
- return null;
414
- }
415
- const vertexBuffer = gl.createBuffer();
572
+ function applyDownsampling(gl, inputTexture, downSampler, vertexBuffer, width, height) {
573
+ gl.useProgram(downSampler.program);
574
+ gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);
575
+ gl.viewport(0, 0, width, height);
416
576
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
417
- gl.bufferData(
418
- gl.ARRAY_BUFFER,
419
- new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
420
- gl.STATIC_DRAW
421
- );
422
- return vertexBuffer;
423
- };
577
+ gl.enableVertexAttribArray(downSampler.uniforms.position);
578
+ gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);
579
+ gl.activeTexture(gl.TEXTURE0);
580
+ gl.bindTexture(gl.TEXTURE_2D, inputTexture);
581
+ gl.uniform1i(downSampler.uniforms.texture, 0);
582
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
583
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
584
+ return downSampler.texture;
585
+ }
586
+
587
+ // src/webgl/index.ts
424
588
  var setupWebGL = (canvas) => {
425
- const gl = canvas.getContext("webgl2", { premultipliedAlpha: false });
589
+ const gl = canvas.getContext("webgl2", {
590
+ antialias: true,
591
+ premultipliedAlpha: true
592
+ });
426
593
  let blurRadius = null;
594
+ let maskBlurRadius = 8;
595
+ const downsampleFactor = 4;
427
596
  if (!gl) {
597
+ console.error("Failed to create WebGL context");
428
598
  return void 0;
429
599
  }
430
600
  gl.enable(gl.BLEND);
431
601
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
602
+ const composite = createCompositeProgram(gl);
603
+ const compositeProgram = composite.program;
604
+ const positionLocation = composite.attribLocations.position;
432
605
  const {
433
- compositeProgram,
434
- blurProgram,
435
- attribLocations: { position: positionLocation },
436
- uniformLocations: {
437
- mask: maskTextureLocation,
438
- frame: frameTextureLocation,
439
- background: bgTextureLocation
440
- },
441
- blurUniforms
442
- } = createShaderProgram(gl);
606
+ mask: maskTextureLocation,
607
+ frame: frameTextureLocation,
608
+ background: bgTextureLocation
609
+ } = composite.uniformLocations;
610
+ const blur = createBlurProgram(gl);
611
+ const blurProgram = blur.program;
612
+ const blurUniforms = blur.uniforms;
613
+ const boxBlur = createBoxBlurProgram(gl);
614
+ const boxBlurProgram = boxBlur.program;
615
+ const boxBlurUniforms = boxBlur.uniforms;
443
616
  const bgTexture = initTexture(gl, 0);
444
617
  const frameTexture = initTexture(gl, 1);
445
618
  const vertexBuffer = createVertexBuffer(gl);
446
- let processTextures = [];
447
- let processFramebuffers = [];
448
- processTextures.push(initTexture(gl, 3));
449
- processTextures.push(initTexture(gl, 4));
450
- processFramebuffers.push(createFramebuffer(gl, processTextures[0], canvas.width, canvas.height));
451
- processFramebuffers.push(createFramebuffer(gl, processTextures[1], canvas.width, canvas.height));
619
+ if (!vertexBuffer) {
620
+ throw new Error("Failed to create vertex buffer");
621
+ }
622
+ let bgBlurTextures = [];
623
+ let bgBlurFrameBuffers = [];
624
+ let blurredMaskTexture = null;
625
+ let finalMaskTextures = [];
626
+ let readMaskIndex = 0;
627
+ let writeMaskIndex = 1;
628
+ bgBlurTextures.push(initTexture(gl, 3));
629
+ bgBlurTextures.push(initTexture(gl, 4));
630
+ const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);
631
+ const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);
632
+ const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);
633
+ bgBlurFrameBuffers.push(
634
+ createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight)
635
+ );
636
+ bgBlurFrameBuffers.push(
637
+ createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight)
638
+ );
639
+ const tempMaskTexture = initTexture(gl, 5);
640
+ const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);
641
+ finalMaskTextures.push(initTexture(gl, 6));
642
+ finalMaskTextures.push(initTexture(gl, 7));
643
+ const finalMaskFrameBuffers = [
644
+ createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),
645
+ createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height)
646
+ ];
452
647
  gl.useProgram(compositeProgram);
453
648
  gl.uniform1i(bgTextureLocation, 0);
454
649
  gl.uniform1i(frameTextureLocation, 1);
455
650
  gl.uniform1i(maskTextureLocation, 2);
456
- let customBackgroundImage = null;
457
- function applyBlur(sourceTexture, width, height) {
458
- if (!blurRadius || !blurProgram || !blurUniforms)
459
- return bgTexture;
460
- gl.useProgram(blurProgram);
461
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
462
- gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);
463
- gl.enableVertexAttribArray(blurUniforms.position);
464
- const texelWidth = 1 / width;
465
- const texelHeight = 1 / height;
466
- gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);
467
- gl.viewport(0, 0, width, height);
468
- gl.activeTexture(gl.TEXTURE0);
469
- gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
470
- gl.uniform1i(blurUniforms.texture, 0);
471
- gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);
472
- gl.uniform2f(blurUniforms.direction, 1, 0);
473
- gl.uniform1f(blurUniforms.radius, blurRadius);
474
- gl.drawArrays(gl.TRIANGLES, 0, 6);
475
- gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);
476
- gl.viewport(0, 0, width, height);
477
- gl.activeTexture(gl.TEXTURE0);
478
- gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);
479
- gl.uniform1i(blurUniforms.texture, 0);
480
- gl.uniform2f(blurUniforms.direction, 0, 1);
481
- gl.drawArrays(gl.TRIANGLES, 0, 6);
482
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
483
- return processTextures[1];
484
- }
485
- function render(frame, mask) {
486
- if (frame.codedWidth === 0 || mask.width === 0) {
651
+ let customBackgroundImage = emptyImageData;
652
+ function renderFrame(frame) {
653
+ if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {
487
654
  return;
488
655
  }
489
656
  const width = frame.displayWidth;
@@ -492,15 +659,33 @@ var setupWebGL = (canvas) => {
492
659
  gl.bindTexture(gl.TEXTURE_2D, frameTexture);
493
660
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
494
661
  let backgroundTexture = bgTexture;
495
- if (customBackgroundImage) {
662
+ if (blurRadius) {
663
+ const downSampledFrameTexture = applyDownsampling(
664
+ gl,
665
+ frameTexture,
666
+ downSampler,
667
+ vertexBuffer,
668
+ bgBlurTextureWidth,
669
+ bgBlurTextureHeight
670
+ );
671
+ backgroundTexture = applyBlur(
672
+ gl,
673
+ downSampledFrameTexture,
674
+ bgBlurTextureWidth,
675
+ bgBlurTextureHeight,
676
+ blurRadius,
677
+ blurProgram,
678
+ blurUniforms,
679
+ vertexBuffer,
680
+ bgBlurFrameBuffers,
681
+ bgBlurTextures
682
+ );
683
+ } else {
496
684
  gl.activeTexture(gl.TEXTURE0);
497
685
  gl.bindTexture(gl.TEXTURE_2D, bgTexture);
498
686
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
499
687
  backgroundTexture = bgTexture;
500
- } else if (blurRadius) {
501
- backgroundTexture = applyBlur(frameTexture, width, height);
502
688
  }
503
- const maskTexture = mask.getAsWebGLTexture();
504
689
  gl.viewport(0, 0, width, height);
505
690
  gl.clearColor(1, 1, 1, 1);
506
691
  gl.clear(gl.COLOR_BUFFER_BIT);
@@ -515,81 +700,89 @@ var setupWebGL = (canvas) => {
515
700
  gl.bindTexture(gl.TEXTURE_2D, frameTexture);
516
701
  gl.uniform1i(frameTextureLocation, 1);
517
702
  gl.activeTexture(gl.TEXTURE2);
518
- gl.bindTexture(gl.TEXTURE_2D, maskTexture);
703
+ gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);
519
704
  gl.uniform1i(maskTextureLocation, 2);
520
705
  gl.drawArrays(gl.TRIANGLES, 0, 6);
521
- mask.close();
522
706
  }
523
707
  async function setBackgroundImage(image) {
524
- customBackgroundImage = null;
708
+ customBackgroundImage = emptyImageData;
525
709
  if (image) {
526
710
  try {
527
- const canvasWidth = canvas.width;
528
- const canvasHeight = canvas.height;
529
- const imgAspect = image.width / image.height;
530
- const canvasAspect = canvasWidth / canvasHeight;
531
- let sx = 0;
532
- let sy = 0;
533
- let sWidth = image.width;
534
- let sHeight = image.height;
535
- if (imgAspect > canvasAspect) {
536
- sWidth = Math.round(image.height * canvasAspect);
537
- sx = Math.round((image.width - sWidth) / 2);
538
- } else if (imgAspect < canvasAspect) {
539
- sHeight = Math.round(image.width / canvasAspect);
540
- sy = Math.round((image.height - sHeight) / 2);
541
- }
542
- const croppedImage = await createImageBitmap(image, sx, sy, sWidth, sHeight, {
543
- resizeWidth: canvasWidth,
544
- resizeHeight: canvasHeight,
545
- resizeQuality: "medium"
546
- });
711
+ const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);
547
712
  customBackgroundImage = croppedImage;
548
- gl.activeTexture(gl.TEXTURE0);
549
- gl.bindTexture(gl.TEXTURE_2D, bgTexture);
550
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, croppedImage);
551
713
  } catch (error) {
552
- console.error("Error processing background image:", error);
553
- customBackgroundImage = image;
554
- gl.activeTexture(gl.TEXTURE0);
555
- gl.bindTexture(gl.TEXTURE_2D, bgTexture);
556
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
714
+ console.error(
715
+ "Error processing background image, falling back to black background:",
716
+ error
717
+ );
557
718
  }
558
- } else {
559
- const emptyImage = new ImageData(2, 2);
560
- emptyImage.data[0] = 0;
561
- emptyImage.data[1] = 0;
562
- emptyImage.data[2] = 0;
563
- emptyImage.data[3] = 0;
564
- gl.activeTexture(gl.TEXTURE0);
565
- gl.bindTexture(gl.TEXTURE_2D, bgTexture);
566
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, emptyImage);
567
719
  }
720
+ gl.activeTexture(gl.TEXTURE0);
721
+ gl.bindTexture(gl.TEXTURE_2D, bgTexture);
722
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
568
723
  }
569
724
  function setBlurRadius(radius) {
570
- blurRadius = radius;
725
+ blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null;
571
726
  setBackgroundImage(null);
572
727
  }
728
+ function updateMask(mask) {
729
+ const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];
730
+ const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];
731
+ applyBlur(
732
+ gl,
733
+ mask,
734
+ canvas.width,
735
+ canvas.height,
736
+ maskBlurRadius || 1,
737
+ boxBlurProgram,
738
+ boxBlurUniforms,
739
+ vertexBuffer,
740
+ tempFramebuffers,
741
+ tempTextures
742
+ );
743
+ readMaskIndex = writeMaskIndex;
744
+ writeMaskIndex = 1 - writeMaskIndex;
745
+ }
573
746
  function cleanup() {
574
747
  gl.deleteProgram(compositeProgram);
575
748
  gl.deleteProgram(blurProgram);
749
+ gl.deleteProgram(boxBlurProgram);
576
750
  gl.deleteTexture(bgTexture);
577
751
  gl.deleteTexture(frameTexture);
578
- for (const texture of processTextures) {
752
+ gl.deleteTexture(tempMaskTexture);
753
+ gl.deleteFramebuffer(tempMaskFrameBuffer);
754
+ for (const texture of bgBlurTextures) {
755
+ gl.deleteTexture(texture);
756
+ }
757
+ for (const framebuffer of bgBlurFrameBuffers) {
758
+ gl.deleteFramebuffer(framebuffer);
759
+ }
760
+ for (const texture of finalMaskTextures) {
579
761
  gl.deleteTexture(texture);
580
762
  }
581
- for (const framebuffer of processFramebuffers) {
763
+ for (const framebuffer of finalMaskFrameBuffers) {
582
764
  gl.deleteFramebuffer(framebuffer);
583
765
  }
584
766
  gl.deleteBuffer(vertexBuffer);
767
+ if (blurredMaskTexture) {
768
+ gl.deleteTexture(blurredMaskTexture);
769
+ }
770
+ if (downSampler) {
771
+ gl.deleteTexture(downSampler.texture);
772
+ gl.deleteFramebuffer(downSampler.framebuffer);
773
+ gl.deleteProgram(downSampler.program);
774
+ }
585
775
  if (customBackgroundImage) {
586
- customBackgroundImage.close();
587
- customBackgroundImage = null;
776
+ if (customBackgroundImage instanceof ImageBitmap) {
777
+ customBackgroundImage.close();
778
+ }
779
+ customBackgroundImage = emptyImageData;
588
780
  }
589
- processTextures = [];
590
- processFramebuffers = [];
781
+ bgBlurTextures = [];
782
+ bgBlurFrameBuffers = [];
783
+ finalMaskTextures = [];
591
784
  }
592
- return { render, setBackgroundImage, setBlurRadius, cleanup };
785
+ return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };
593
786
  };
594
787
 
595
788
  // src/transformers/VideoTransformer.ts
@@ -640,6 +833,7 @@ var BackgroundProcessor = class extends VideoTransformer {
640
833
  constructor(opts) {
641
834
  super();
642
835
  this.backgroundImage = null;
836
+ this.segmentationTimeMs = 0;
643
837
  this.options = opts;
644
838
  this.update(opts);
645
839
  }
@@ -691,7 +885,7 @@ var BackgroundProcessor = class extends VideoTransformer {
691
885
  (_a = this.gl) == null ? void 0 : _a.setBackgroundImage(imageData);
692
886
  }
693
887
  async transform(frame, controller) {
694
- var _a;
888
+ var _a, _b;
695
889
  try {
696
890
  if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
697
891
  console.debug("empty frame detected, ignoring");
@@ -701,37 +895,49 @@ var BackgroundProcessor = class extends VideoTransformer {
701
895
  controller.enqueue(frame);
702
896
  return;
703
897
  }
898
+ const frameTimeMs = Date.now();
704
899
  if (!this.canvas) {
705
900
  throw TypeError("Canvas needs to be initialized first");
706
901
  }
707
902
  this.canvas.width = frame.displayWidth;
708
903
  this.canvas.height = frame.displayHeight;
709
- let startTimeMs = performance.now();
710
- (_a = this.imageSegmenter) == null ? void 0 : _a.segmentForVideo(frame, startTimeMs, (result) => {
711
- var _a2, _b;
712
- const segmentationTimeMs = performance.now() - startTimeMs;
713
- this.segmentationResults = result;
714
- this.drawFrame(frame);
715
- if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
716
- const newFrame = new VideoFrame(this.canvas, {
717
- timestamp: frame.timestamp || Date.now()
904
+ const segmentationPromise = new Promise((resolve, reject) => {
905
+ var _a2;
906
+ try {
907
+ let segmentationStartTimeMs = performance.now();
908
+ (_a2 = this.imageSegmenter) == null ? void 0 : _a2.segmentForVideo(frame, segmentationStartTimeMs, (result) => {
909
+ this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;
910
+ this.segmentationResults = result;
911
+ this.updateMask(result.categoryMask);
912
+ result.close();
913
+ resolve();
718
914
  });
719
- const filterTimeMs = performance.now() - startTimeMs - segmentationTimeMs;
720
- const stats = {
721
- processingTimeMs: performance.now() - startTimeMs,
722
- segmentationTimeMs,
723
- filterTimeMs
724
- };
725
- (_b = (_a2 = this.options).onFrameProcessed) == null ? void 0 : _b.call(_a2, stats);
726
- controller.enqueue(newFrame);
727
- } else {
728
- controller.enqueue(frame);
915
+ } catch (e) {
916
+ reject(e);
729
917
  }
730
- frame.close();
731
918
  });
919
+ const filterStartTimeMs = performance.now();
920
+ this.drawFrame(frame);
921
+ if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
922
+ const newFrame = new VideoFrame(this.canvas, {
923
+ timestamp: frame.timestamp || frameTimeMs
924
+ });
925
+ controller.enqueue(newFrame);
926
+ const filterTimeMs = performance.now() - filterStartTimeMs;
927
+ const stats = {
928
+ processingTimeMs: this.segmentationTimeMs + filterTimeMs,
929
+ segmentationTimeMs: this.segmentationTimeMs,
930
+ filterTimeMs
931
+ };
932
+ (_b = (_a = this.options).onFrameProcessed) == null ? void 0 : _b.call(_a, stats);
933
+ } else {
934
+ controller.enqueue(frame);
935
+ }
936
+ await segmentationPromise;
732
937
  } catch (e) {
733
938
  console.error("Error while processing frame: ", e);
734
- frame == null ? void 0 : frame.close();
939
+ } finally {
940
+ frame.close();
735
941
  }
736
942
  }
737
943
  async update(opts) {
@@ -744,12 +950,16 @@ var BackgroundProcessor = class extends VideoTransformer {
744
950
  }
745
951
  }
746
952
  async drawFrame(frame) {
747
- if (!this.canvas || !this.gl || !this.segmentationResults || !this.inputVideo)
953
+ var _a;
954
+ if (!this.gl)
748
955
  return;
749
- const mask = this.segmentationResults.categoryMask;
750
- if (mask) {
751
- this.gl.render(frame, mask);
752
- }
956
+ (_a = this.gl) == null ? void 0 : _a.renderFrame(frame);
957
+ }
958
+ async updateMask(mask) {
959
+ var _a;
960
+ if (!mask)
961
+ return;
962
+ (_a = this.gl) == null ? void 0 : _a.updateMask(mask.getAsWebGLTexture());
753
963
  }
754
964
  };
755
965
 
@@ -797,17 +1007,14 @@ var BackgroundProcessor2 = (options, name = "background-processor") => {
797
1007
  onFrameProcessed,
798
1008
  ...processorOpts
799
1009
  } = options;
800
- const processor = new ProcessorWrapper(
801
- new BackgroundProcessor({
802
- blurRadius,
803
- imagePath,
804
- segmenterOptions,
805
- assetPaths,
806
- onFrameProcessed
807
- }),
808
- name,
809
- processorOpts
810
- );
1010
+ const transformer = new BackgroundProcessor({
1011
+ blurRadius,
1012
+ imagePath,
1013
+ segmenterOptions,
1014
+ assetPaths,
1015
+ onFrameProcessed
1016
+ });
1017
+ const processor = new ProcessorWrapper(transformer, name, processorOpts);
811
1018
  return processor;
812
1019
  };
813
1020