@livekit/track-processors 0.5.2 → 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.
- package/dist/index.js +380 -221
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +380 -221
- package/dist/index.mjs.map +1 -1
- package/dist/src/webgl/index.d.ts +0 -2
- package/dist/src/webgl/shader-programs/blurShader.d.ts +14 -0
- package/dist/src/webgl/shader-programs/boxBlurShader.d.ts +16 -0
- package/dist/src/webgl/shader-programs/compositeShader.d.ts +18 -0
- package/dist/src/webgl/shader-programs/vertexShader.d.ts +1 -0
- package/dist/src/webgl/utils.d.ts +23 -0
- package/package.json +2 -2
- package/src/index.ts +9 -11
- package/src/webgl/index.ts +119 -339
- package/src/webgl/shader-programs/blurShader.ts +140 -0
- package/src/webgl/shader-programs/boxBlurShader.ts +98 -0
- package/src/webgl/shader-programs/compositeShader.ts +102 -0
- package/src/webgl/shader-programs/vertexShader.ts +11 -0
- package/src/webgl/utils.ts +110 -0
package/dist/index.mjs
CHANGED
|
@@ -248,17 +248,93 @@ import * as vision from "@mediapipe/tasks-vision";
|
|
|
248
248
|
|
|
249
249
|
// package.json
|
|
250
250
|
var dependencies = {
|
|
251
|
-
"@mediapipe/tasks-vision": "
|
|
251
|
+
"@mediapipe/tasks-vision": "0.10.14"
|
|
252
252
|
};
|
|
253
253
|
|
|
254
|
-
// src/webgl/
|
|
255
|
-
|
|
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 createFramebuffer(gl, texture, width, height) {
|
|
268
|
+
const framebuffer = gl.createFramebuffer();
|
|
269
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
270
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
271
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
272
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
273
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
274
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
275
|
+
throw new Error("Framebuffer not complete");
|
|
276
|
+
}
|
|
277
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
278
|
+
return framebuffer;
|
|
279
|
+
}
|
|
280
|
+
function createVertexBuffer(gl) {
|
|
281
|
+
const vertexBuffer = gl.createBuffer();
|
|
282
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
283
|
+
gl.bufferData(
|
|
284
|
+
gl.ARRAY_BUFFER,
|
|
285
|
+
new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
|
|
286
|
+
gl.STATIC_DRAW
|
|
287
|
+
);
|
|
288
|
+
return vertexBuffer;
|
|
289
|
+
}
|
|
290
|
+
async function resizeImageToCover(image, targetWidth, targetHeight) {
|
|
291
|
+
const imgAspect = image.width / image.height;
|
|
292
|
+
const targetAspect = targetWidth / targetHeight;
|
|
293
|
+
let sx = 0;
|
|
294
|
+
let sy = 0;
|
|
295
|
+
let sWidth = image.width;
|
|
296
|
+
let sHeight = image.height;
|
|
297
|
+
if (imgAspect > targetAspect) {
|
|
298
|
+
sWidth = Math.round(image.height * targetAspect);
|
|
299
|
+
sx = Math.round((image.width - sWidth) / 2);
|
|
300
|
+
} else if (imgAspect < targetAspect) {
|
|
301
|
+
sHeight = Math.round(image.width / targetAspect);
|
|
302
|
+
sy = Math.round((image.height - sHeight) / 2);
|
|
303
|
+
}
|
|
304
|
+
return createImageBitmap(image, sx, sy, sWidth, sHeight, {
|
|
305
|
+
resizeWidth: targetWidth,
|
|
306
|
+
resizeHeight: targetHeight,
|
|
307
|
+
resizeQuality: "medium"
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
var emptyImageData = new ImageData(2, 2);
|
|
311
|
+
emptyImageData.data[0] = 0;
|
|
312
|
+
emptyImageData.data[1] = 0;
|
|
313
|
+
emptyImageData.data[2] = 0;
|
|
314
|
+
emptyImageData.data[3] = 0;
|
|
315
|
+
var glsl = (source) => source;
|
|
316
|
+
|
|
317
|
+
// src/webgl/shader-programs/vertexShader.ts
|
|
318
|
+
var vertexShaderSource = (flipY = true) => `#version 300 es
|
|
319
|
+
in vec2 position;
|
|
320
|
+
out vec2 texCoords;
|
|
321
|
+
|
|
322
|
+
void main() {
|
|
323
|
+
texCoords = (position + 1.0) / 2.0;
|
|
324
|
+
texCoords.y = ${flipY ? "1.0 - texCoords.y" : "texCoords.y"};
|
|
325
|
+
gl_Position = vec4(position, 0, 1.0);
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
|
|
329
|
+
// src/webgl/shader-programs/blurShader.ts
|
|
330
|
+
var blurFragmentShader = glsl`#version 300 es
|
|
256
331
|
precision highp float;
|
|
257
|
-
|
|
332
|
+
in vec2 texCoords;
|
|
258
333
|
uniform sampler2D u_texture;
|
|
259
334
|
uniform vec2 u_texelSize;
|
|
260
335
|
uniform vec2 u_direction;
|
|
261
336
|
uniform float u_radius;
|
|
337
|
+
out vec4 fragColor;
|
|
262
338
|
|
|
263
339
|
void main() {
|
|
264
340
|
float sigma = u_radius;
|
|
@@ -273,68 +349,17 @@ var blurFragmentShader = `
|
|
|
273
349
|
if (abs(offset) > float(radius)) continue;
|
|
274
350
|
float weight = exp(-(offset * offset) / twoSigmaSq);
|
|
275
351
|
vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;
|
|
276
|
-
result +=
|
|
352
|
+
result += texture(u_texture, sampleCoord).rgb * weight;
|
|
277
353
|
totalWeight += weight;
|
|
278
354
|
}
|
|
279
355
|
|
|
280
|
-
|
|
356
|
+
fragColor = vec4(result / totalWeight, 1.0);
|
|
281
357
|
}
|
|
282
358
|
`;
|
|
283
|
-
|
|
284
|
-
const
|
|
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);
|
|
359
|
+
function createBlurProgram(gl) {
|
|
360
|
+
const blurFrag = gl.createShader(gl.FRAGMENT_SHADER);
|
|
336
361
|
if (!blurFrag) {
|
|
337
|
-
throw Error("
|
|
362
|
+
throw Error("cannot create blur shader");
|
|
338
363
|
}
|
|
339
364
|
gl.shaderSource(blurFrag, blurFragmentShader);
|
|
340
365
|
gl.compileShader(blurFrag);
|
|
@@ -342,15 +367,15 @@ var createShaderProgram = (gl) => {
|
|
|
342
367
|
const info = gl.getShaderInfoLog(blurFrag);
|
|
343
368
|
throw Error(`Failed to compile blur shader: ${info}`);
|
|
344
369
|
}
|
|
345
|
-
blurVertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
370
|
+
const blurVertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
346
371
|
if (!blurVertexShader) {
|
|
347
|
-
throw Error("
|
|
372
|
+
throw Error("cannot create blur vertex shader");
|
|
348
373
|
}
|
|
349
|
-
gl.shaderSource(blurVertexShader,
|
|
374
|
+
gl.shaderSource(blurVertexShader, vertexShaderSource());
|
|
350
375
|
gl.compileShader(blurVertexShader);
|
|
351
|
-
blurProgram = gl.createProgram();
|
|
376
|
+
const blurProgram = gl.createProgram();
|
|
352
377
|
if (!blurProgram) {
|
|
353
|
-
throw Error("
|
|
378
|
+
throw Error("cannot create blur program");
|
|
354
379
|
}
|
|
355
380
|
gl.attachShader(blurProgram, blurVertexShader);
|
|
356
381
|
gl.attachShader(blurProgram, blurFrag);
|
|
@@ -359,7 +384,7 @@ var createShaderProgram = (gl) => {
|
|
|
359
384
|
const info = gl.getProgramInfoLog(blurProgram);
|
|
360
385
|
throw Error(`Failed to link blur program: ${info}`);
|
|
361
386
|
}
|
|
362
|
-
blurUniforms = {
|
|
387
|
+
const blurUniforms = {
|
|
363
388
|
position: gl.getAttribLocation(blurProgram, "position"),
|
|
364
389
|
texture: gl.getUniformLocation(blurProgram, "u_texture"),
|
|
365
390
|
texelSize: gl.getUniformLocation(blurProgram, "u_texelSize"),
|
|
@@ -367,121 +392,253 @@ var createShaderProgram = (gl) => {
|
|
|
367
392
|
radius: gl.getUniformLocation(blurProgram, "u_radius")
|
|
368
393
|
};
|
|
369
394
|
return {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
|
395
|
+
program: blurProgram,
|
|
396
|
+
shader: blurFrag,
|
|
397
|
+
vertexShader: blurVertexShader,
|
|
398
|
+
uniforms: blurUniforms
|
|
384
399
|
};
|
|
385
|
-
};
|
|
386
|
-
function initTexture(gl, texIndex) {
|
|
387
|
-
const texRef = gl.TEXTURE0 + texIndex;
|
|
388
|
-
gl.activeTexture(texRef);
|
|
389
|
-
const texture = gl.createTexture();
|
|
390
|
-
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);
|
|
393
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
394
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
395
|
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
396
|
-
return texture;
|
|
397
400
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
gl.
|
|
401
|
-
gl.
|
|
402
|
-
gl.
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
401
|
+
function applyBlur(gl, sourceTexture, width, height, blurRadius, blurProgram, blurUniforms, vertexBuffer, processFramebuffers, processTextures) {
|
|
402
|
+
gl.useProgram(blurProgram);
|
|
403
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
404
|
+
gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);
|
|
405
|
+
gl.enableVertexAttribArray(blurUniforms.position);
|
|
406
|
+
const texelWidth = 1 / width;
|
|
407
|
+
const texelHeight = 1 / height;
|
|
408
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);
|
|
409
|
+
gl.viewport(0, 0, width, height);
|
|
410
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
411
|
+
gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
|
|
412
|
+
gl.uniform1i(blurUniforms.texture, 0);
|
|
413
|
+
gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);
|
|
414
|
+
gl.uniform2f(blurUniforms.direction, 1, 0);
|
|
415
|
+
gl.uniform1f(blurUniforms.radius, blurRadius);
|
|
416
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
417
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);
|
|
418
|
+
gl.viewport(0, 0, width, height);
|
|
419
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
420
|
+
gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);
|
|
421
|
+
gl.uniform1i(blurUniforms.texture, 0);
|
|
422
|
+
gl.uniform2f(blurUniforms.direction, 0, 1);
|
|
423
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
408
424
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
409
|
-
return
|
|
425
|
+
return processTextures[1];
|
|
410
426
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
427
|
+
|
|
428
|
+
// src/webgl/shader-programs/boxBlurShader.ts
|
|
429
|
+
var boxBlurFragmentShader = glsl`#version 300 es
|
|
430
|
+
precision mediump float;
|
|
431
|
+
|
|
432
|
+
in vec2 texCoords;
|
|
433
|
+
|
|
434
|
+
uniform sampler2D u_texture;
|
|
435
|
+
uniform vec2 u_texelSize; // 1.0 / texture size
|
|
436
|
+
uniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical
|
|
437
|
+
uniform float u_radius; // blur radius in texels
|
|
438
|
+
|
|
439
|
+
out vec4 fragColor;
|
|
440
|
+
|
|
441
|
+
void main() {
|
|
442
|
+
vec3 sum = vec3(0.0);
|
|
443
|
+
float count = 0.0;
|
|
444
|
+
|
|
445
|
+
// Limit radius to avoid excessive loop cost
|
|
446
|
+
const int MAX_RADIUS = 16;
|
|
447
|
+
int radius = int(min(float(MAX_RADIUS), u_radius));
|
|
448
|
+
|
|
449
|
+
for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {
|
|
450
|
+
if (abs(i) > radius) continue;
|
|
451
|
+
|
|
452
|
+
vec2 offset = u_direction * u_texelSize * float(i);
|
|
453
|
+
sum += texture(u_texture, texCoords + offset).rgb;
|
|
454
|
+
count += 1.0;
|
|
414
455
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
}
|
|
456
|
+
|
|
457
|
+
fragColor = vec4(sum / count, 1.0);
|
|
458
|
+
}
|
|
459
|
+
`;
|
|
460
|
+
function createBoxBlurProgram(gl) {
|
|
461
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
462
|
+
if (!vertexShader) {
|
|
463
|
+
throw Error("cannot create vertex shader");
|
|
464
|
+
}
|
|
465
|
+
gl.shaderSource(vertexShader, vertexShaderSource());
|
|
466
|
+
gl.compileShader(vertexShader);
|
|
467
|
+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
468
|
+
const info = gl.getShaderInfoLog(vertexShader);
|
|
469
|
+
throw Error(`Failed to compile vertex shader: ${info}`);
|
|
470
|
+
}
|
|
471
|
+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
472
|
+
if (!fragmentShader) {
|
|
473
|
+
throw Error("cannot create fragment shader");
|
|
474
|
+
}
|
|
475
|
+
gl.shaderSource(fragmentShader, boxBlurFragmentShader);
|
|
476
|
+
gl.compileShader(fragmentShader);
|
|
477
|
+
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
478
|
+
const info = gl.getShaderInfoLog(fragmentShader);
|
|
479
|
+
throw Error(`Failed to compile box blur shader: ${info}`);
|
|
480
|
+
}
|
|
481
|
+
const program = gl.createProgram();
|
|
482
|
+
if (!program) {
|
|
483
|
+
throw Error("cannot create box blur program");
|
|
484
|
+
}
|
|
485
|
+
gl.attachShader(program, vertexShader);
|
|
486
|
+
gl.attachShader(program, fragmentShader);
|
|
487
|
+
gl.linkProgram(program);
|
|
488
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
489
|
+
const info = gl.getProgramInfoLog(program);
|
|
490
|
+
throw Error(`Failed to link box blur program: ${info}`);
|
|
491
|
+
}
|
|
492
|
+
const uniforms = {
|
|
493
|
+
position: gl.getAttribLocation(program, "position"),
|
|
494
|
+
texture: gl.getUniformLocation(program, "u_texture"),
|
|
495
|
+
texelSize: gl.getUniformLocation(program, "u_texelSize"),
|
|
496
|
+
direction: gl.getUniformLocation(program, "u_direction"),
|
|
497
|
+
radius: gl.getUniformLocation(program, "u_radius")
|
|
498
|
+
};
|
|
499
|
+
return {
|
|
500
|
+
program,
|
|
501
|
+
vertexShader,
|
|
502
|
+
fragmentShader,
|
|
503
|
+
uniforms
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/webgl/shader-programs/compositeShader.ts
|
|
508
|
+
var compositeFragmentShader = glsl`#version 300 es
|
|
509
|
+
precision highp float;
|
|
510
|
+
in vec2 texCoords;
|
|
511
|
+
uniform sampler2D background;
|
|
512
|
+
uniform sampler2D frame;
|
|
513
|
+
uniform sampler2D mask;
|
|
514
|
+
out vec4 fragColor;
|
|
515
|
+
|
|
516
|
+
void main() {
|
|
517
|
+
|
|
518
|
+
vec4 frameTex = texture(frame, texCoords);
|
|
519
|
+
vec4 bgTex = texture(background, texCoords);
|
|
520
|
+
|
|
521
|
+
float maskVal = texture(mask, texCoords).r;
|
|
522
|
+
|
|
523
|
+
// Compute screen-space gradient to detect edge sharpness
|
|
524
|
+
float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));
|
|
525
|
+
|
|
526
|
+
float edgeSoftness = 2.0; // higher = softer
|
|
527
|
+
|
|
528
|
+
// Create a smooth edge around binary transition
|
|
529
|
+
float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);
|
|
530
|
+
|
|
531
|
+
// Optional: preserve frame alpha, or override as fully opaque
|
|
532
|
+
vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);
|
|
533
|
+
|
|
534
|
+
fragColor = blended;
|
|
535
|
+
|
|
536
|
+
}
|
|
537
|
+
`;
|
|
538
|
+
function createCompositeProgram(gl) {
|
|
539
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
540
|
+
if (!vertexShader) {
|
|
541
|
+
throw Error("cannot create vertex shader");
|
|
542
|
+
}
|
|
543
|
+
gl.shaderSource(vertexShader, vertexShaderSource());
|
|
544
|
+
gl.compileShader(vertexShader);
|
|
545
|
+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
546
|
+
const info = gl.getShaderInfoLog(vertexShader);
|
|
547
|
+
throw Error(`Failed to compile vertex shader: ${info}`);
|
|
548
|
+
}
|
|
549
|
+
const compositeShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
550
|
+
if (!compositeShader) {
|
|
551
|
+
throw Error("cannot create fragment shader");
|
|
552
|
+
}
|
|
553
|
+
gl.shaderSource(compositeShader, compositeFragmentShader);
|
|
554
|
+
gl.compileShader(compositeShader);
|
|
555
|
+
if (!gl.getShaderParameter(compositeShader, gl.COMPILE_STATUS)) {
|
|
556
|
+
const info = gl.getShaderInfoLog(compositeShader);
|
|
557
|
+
throw Error(`Failed to compile composite shader: ${info}`);
|
|
558
|
+
}
|
|
559
|
+
const compositeProgram = gl.createProgram();
|
|
560
|
+
if (!compositeProgram) {
|
|
561
|
+
throw Error("cannot create composite program");
|
|
562
|
+
}
|
|
563
|
+
gl.attachShader(compositeProgram, vertexShader);
|
|
564
|
+
gl.attachShader(compositeProgram, compositeShader);
|
|
565
|
+
gl.linkProgram(compositeProgram);
|
|
566
|
+
if (!gl.getProgramParameter(compositeProgram, gl.LINK_STATUS)) {
|
|
567
|
+
const info = gl.getProgramInfoLog(compositeProgram);
|
|
568
|
+
throw Error(`Failed to link composite program: ${info}`);
|
|
569
|
+
}
|
|
570
|
+
const attribLocations = {
|
|
571
|
+
position: gl.getAttribLocation(compositeProgram, "position")
|
|
572
|
+
};
|
|
573
|
+
const uniformLocations = {
|
|
574
|
+
mask: gl.getUniformLocation(compositeProgram, "mask"),
|
|
575
|
+
frame: gl.getUniformLocation(compositeProgram, "frame"),
|
|
576
|
+
background: gl.getUniformLocation(compositeProgram, "background"),
|
|
577
|
+
stepWidth: gl.getUniformLocation(compositeProgram, "u_stepWidth")
|
|
578
|
+
};
|
|
579
|
+
return {
|
|
580
|
+
program: compositeProgram,
|
|
581
|
+
vertexShader,
|
|
582
|
+
fragmentShader: compositeShader,
|
|
583
|
+
attribLocations,
|
|
584
|
+
uniformLocations
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/webgl/index.ts
|
|
424
589
|
var setupWebGL = (canvas) => {
|
|
425
|
-
const gl = canvas.getContext("webgl2", {
|
|
590
|
+
const gl = canvas.getContext("webgl2", {
|
|
591
|
+
antialias: true,
|
|
592
|
+
premultipliedAlpha: true
|
|
593
|
+
});
|
|
426
594
|
let blurRadius = null;
|
|
427
595
|
if (!gl) {
|
|
596
|
+
console.error("Failed to create WebGL context");
|
|
428
597
|
return void 0;
|
|
429
598
|
}
|
|
430
599
|
gl.enable(gl.BLEND);
|
|
431
600
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
601
|
+
const composite = createCompositeProgram(gl);
|
|
602
|
+
const compositeProgram = composite.program;
|
|
603
|
+
const positionLocation = composite.attribLocations.position;
|
|
432
604
|
const {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
605
|
+
mask: maskTextureLocation,
|
|
606
|
+
frame: frameTextureLocation,
|
|
607
|
+
background: bgTextureLocation
|
|
608
|
+
} = composite.uniformLocations;
|
|
609
|
+
const blur = createBlurProgram(gl);
|
|
610
|
+
const blurProgram = blur.program;
|
|
611
|
+
const blurUniforms = blur.uniforms;
|
|
612
|
+
const boxBlur = createBoxBlurProgram(gl);
|
|
613
|
+
const boxBlurProgram = boxBlur.program;
|
|
614
|
+
const boxBlurUniforms = boxBlur.uniforms;
|
|
443
615
|
const bgTexture = initTexture(gl, 0);
|
|
444
616
|
const frameTexture = initTexture(gl, 1);
|
|
445
617
|
const vertexBuffer = createVertexBuffer(gl);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
618
|
+
if (!vertexBuffer) {
|
|
619
|
+
throw new Error("Failed to create vertex buffer");
|
|
620
|
+
}
|
|
621
|
+
let bgBlurTextures = [];
|
|
622
|
+
let bgBlurFrameBuffers = [];
|
|
623
|
+
let maskBlurTextures = [];
|
|
624
|
+
let maskBlurFrameBuffers = [];
|
|
625
|
+
bgBlurTextures.push(initTexture(gl, 3));
|
|
626
|
+
bgBlurTextures.push(initTexture(gl, 4));
|
|
627
|
+
bgBlurFrameBuffers.push(createFramebuffer(gl, bgBlurTextures[0], canvas.width, canvas.height));
|
|
628
|
+
bgBlurFrameBuffers.push(createFramebuffer(gl, bgBlurTextures[1], canvas.width, canvas.height));
|
|
629
|
+
maskBlurTextures.push(initTexture(gl, 5));
|
|
630
|
+
maskBlurTextures.push(initTexture(gl, 6));
|
|
631
|
+
maskBlurFrameBuffers.push(
|
|
632
|
+
createFramebuffer(gl, maskBlurTextures[0], canvas.width, canvas.height)
|
|
633
|
+
);
|
|
634
|
+
maskBlurFrameBuffers.push(
|
|
635
|
+
createFramebuffer(gl, maskBlurTextures[1], canvas.width, canvas.height)
|
|
636
|
+
);
|
|
452
637
|
gl.useProgram(compositeProgram);
|
|
453
638
|
gl.uniform1i(bgTextureLocation, 0);
|
|
454
639
|
gl.uniform1i(frameTextureLocation, 1);
|
|
455
640
|
gl.uniform1i(maskTextureLocation, 2);
|
|
456
|
-
let customBackgroundImage =
|
|
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
|
-
}
|
|
641
|
+
let customBackgroundImage = emptyImageData;
|
|
485
642
|
function render(frame, mask) {
|
|
486
643
|
if (frame.codedWidth === 0 || mask.width === 0) {
|
|
487
644
|
return;
|
|
@@ -492,15 +649,38 @@ var setupWebGL = (canvas) => {
|
|
|
492
649
|
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
493
650
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
|
|
494
651
|
let backgroundTexture = bgTexture;
|
|
495
|
-
if (
|
|
652
|
+
if (blurRadius) {
|
|
653
|
+
backgroundTexture = applyBlur(
|
|
654
|
+
gl,
|
|
655
|
+
frameTexture,
|
|
656
|
+
width,
|
|
657
|
+
height,
|
|
658
|
+
blurRadius,
|
|
659
|
+
blurProgram,
|
|
660
|
+
blurUniforms,
|
|
661
|
+
vertexBuffer,
|
|
662
|
+
bgBlurFrameBuffers,
|
|
663
|
+
bgBlurTextures
|
|
664
|
+
);
|
|
665
|
+
} else {
|
|
496
666
|
gl.activeTexture(gl.TEXTURE0);
|
|
497
667
|
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
498
668
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
|
|
499
669
|
backgroundTexture = bgTexture;
|
|
500
|
-
} else if (blurRadius) {
|
|
501
|
-
backgroundTexture = applyBlur(frameTexture, width, height);
|
|
502
670
|
}
|
|
503
|
-
const
|
|
671
|
+
const blurredMaskTexture = applyBlur(
|
|
672
|
+
gl,
|
|
673
|
+
mask.getAsWebGLTexture(),
|
|
674
|
+
width,
|
|
675
|
+
height,
|
|
676
|
+
blurRadius || 1,
|
|
677
|
+
// Use a default blur radius if not set
|
|
678
|
+
boxBlurProgram,
|
|
679
|
+
boxBlurUniforms,
|
|
680
|
+
vertexBuffer,
|
|
681
|
+
maskBlurFrameBuffers,
|
|
682
|
+
maskBlurTextures
|
|
683
|
+
);
|
|
504
684
|
gl.viewport(0, 0, width, height);
|
|
505
685
|
gl.clearColor(1, 1, 1, 1);
|
|
506
686
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
@@ -515,56 +695,27 @@ var setupWebGL = (canvas) => {
|
|
|
515
695
|
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
516
696
|
gl.uniform1i(frameTextureLocation, 1);
|
|
517
697
|
gl.activeTexture(gl.TEXTURE2);
|
|
518
|
-
gl.bindTexture(gl.TEXTURE_2D,
|
|
698
|
+
gl.bindTexture(gl.TEXTURE_2D, blurredMaskTexture);
|
|
519
699
|
gl.uniform1i(maskTextureLocation, 2);
|
|
520
700
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
521
701
|
mask.close();
|
|
522
702
|
}
|
|
523
703
|
async function setBackgroundImage(image) {
|
|
524
|
-
customBackgroundImage =
|
|
704
|
+
customBackgroundImage = emptyImageData;
|
|
525
705
|
if (image) {
|
|
526
706
|
try {
|
|
527
|
-
const
|
|
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
|
-
});
|
|
707
|
+
const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);
|
|
547
708
|
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
709
|
} catch (error) {
|
|
552
|
-
console.error(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
710
|
+
console.error(
|
|
711
|
+
"Error processing background image, falling back to black background:",
|
|
712
|
+
error
|
|
713
|
+
);
|
|
557
714
|
}
|
|
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
715
|
}
|
|
716
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
717
|
+
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
718
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
|
|
568
719
|
}
|
|
569
720
|
function setBlurRadius(radius) {
|
|
570
721
|
blurRadius = radius;
|
|
@@ -573,21 +724,32 @@ var setupWebGL = (canvas) => {
|
|
|
573
724
|
function cleanup() {
|
|
574
725
|
gl.deleteProgram(compositeProgram);
|
|
575
726
|
gl.deleteProgram(blurProgram);
|
|
727
|
+
gl.deleteProgram(boxBlurProgram);
|
|
576
728
|
gl.deleteTexture(bgTexture);
|
|
577
729
|
gl.deleteTexture(frameTexture);
|
|
578
|
-
for (const texture of
|
|
730
|
+
for (const texture of bgBlurTextures) {
|
|
579
731
|
gl.deleteTexture(texture);
|
|
580
732
|
}
|
|
581
|
-
for (const framebuffer of
|
|
733
|
+
for (const framebuffer of bgBlurFrameBuffers) {
|
|
734
|
+
gl.deleteFramebuffer(framebuffer);
|
|
735
|
+
}
|
|
736
|
+
for (const texture of maskBlurTextures) {
|
|
737
|
+
gl.deleteTexture(texture);
|
|
738
|
+
}
|
|
739
|
+
for (const framebuffer of maskBlurFrameBuffers) {
|
|
582
740
|
gl.deleteFramebuffer(framebuffer);
|
|
583
741
|
}
|
|
584
742
|
gl.deleteBuffer(vertexBuffer);
|
|
585
743
|
if (customBackgroundImage) {
|
|
586
|
-
customBackgroundImage
|
|
587
|
-
|
|
744
|
+
if (customBackgroundImage instanceof ImageBitmap) {
|
|
745
|
+
customBackgroundImage.close();
|
|
746
|
+
}
|
|
747
|
+
customBackgroundImage = emptyImageData;
|
|
588
748
|
}
|
|
589
|
-
|
|
590
|
-
|
|
749
|
+
bgBlurTextures = [];
|
|
750
|
+
bgBlurFrameBuffers = [];
|
|
751
|
+
maskBlurTextures = [];
|
|
752
|
+
maskBlurFrameBuffers = [];
|
|
591
753
|
}
|
|
592
754
|
return { render, setBackgroundImage, setBlurRadius, cleanup };
|
|
593
755
|
};
|
|
@@ -797,17 +959,14 @@ var BackgroundProcessor2 = (options, name = "background-processor") => {
|
|
|
797
959
|
onFrameProcessed,
|
|
798
960
|
...processorOpts
|
|
799
961
|
} = options;
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
name,
|
|
809
|
-
processorOpts
|
|
810
|
-
);
|
|
962
|
+
const transformer = new BackgroundProcessor({
|
|
963
|
+
blurRadius,
|
|
964
|
+
imagePath,
|
|
965
|
+
segmenterOptions,
|
|
966
|
+
assetPaths,
|
|
967
|
+
onFrameProcessed
|
|
968
|
+
});
|
|
969
|
+
const processor = new ProcessorWrapper(transformer, name, processorOpts);
|
|
811
970
|
return processor;
|
|
812
971
|
};
|
|
813
972
|
export {
|