@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 +474 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +474 -267
- package/dist/index.mjs.map +1 -1
- package/dist/src/transformers/BackgroundTransformer.d.ts +3 -1
- package/dist/src/webgl/index.d.ts +2 -4
- 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/downSampler.d.ts +12 -0
- package/dist/src/webgl/shader-programs/vertexShader.d.ts +1 -0
- package/dist/src/webgl/utils.d.ts +25 -0
- package/package.json +1 -1
- package/src/index.ts +9 -11
- package/src/transformers/BackgroundTransformer.ts +42 -28
- package/src/webgl/index.ts +180 -345
- package/src/webgl/shader-programs/blurShader.ts +108 -0
- package/src/webgl/shader-programs/boxBlurShader.ts +60 -0
- package/src/webgl/shader-programs/compositeShader.ts +64 -0
- package/src/webgl/shader-programs/downSampler.ts +94 -0
- package/src/webgl/shader-programs/vertexShader.ts +11 -0
- package/src/webgl/utils.ts +142 -0
|
@@ -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 };
|