shaderpad 1.0.0-beta.26 → 1.0.0-beta.28

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/README.md CHANGED
@@ -347,6 +347,7 @@ const shader = new ShaderPad(fragmentShaderSrc, {
347
347
  | `u_leftEye` | vec2[maxFaces] | Left eye positions |
348
348
  | `u_rightEye` | vec2[maxFaces] | Right eye positions |
349
349
  | `u_noseTip` | vec2[maxFaces] | Nose tip positions |
350
+ | `u_mouth` | vec2[maxFaces] | Mouth center positions |
350
351
  | `u_faceMask` | sampler2D | Face mask texture (R: mouth, G: face, B: eyes) |
351
352
  | `u_faceCenter` | vec2[maxFaces] | Center positions of the face mask bounding boxes |
352
353
 
@@ -1,12 +1,13 @@
1
- "use strict";var P=Object.create;var T=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var B=(c,s)=>{for(var r in s)T(c,r,{get:s[r],enumerable:!0})},R=(c,s,r,_)=>{if(s&&typeof s=="object"||typeof s=="function")for(let i of A(s))!U.call(c,i)&&i!==r&&T(c,i,{get:()=>s[i],enumerable:!(_=z(s,i))||_.enumerable});return c};var I=(c,s,r)=>(r=c!=null?P(D(c)):{},R(s||!c||!c.__esModule?T(r,"default",{value:c,enumerable:!0}):r,c)),G=c=>R(T({},"__esModule",{value:!0}),c);var W={};B(W,{default:()=>V});module.exports=G(W);var f={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95]};function $(c){let{textureName:s,options:r}=c,_="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(i,v){let{uniforms:F,injectGLSL:N}=v,l=null,b=null,y=-1,x=new Map,m=r?.maxFaces??1,S=512,w=512,u=document.createElement("canvas");u.width=S,u.height=w;let p=u.getContext("2d");p.globalCompositeOperation="lighten";async function O(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");b=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),l=await e.createFromOptions(b,{baseOptions:{modelAssetPath:r?.modelPath||_},runningMode:"VIDEO",numFaces:r?.maxFaces??1,minFaceDetectionConfidence:r?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:r?.minFacePresenceConfidence??.5,minTrackingConfidence:r?.minTrackingConfidence??.5,outputFaceBlendshapes:r?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:r?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function Y(n){let e=1/0,a=-1/0,o=1/0,t=-1/0;for(let h of n)e=Math.min(e,h.x),a=Math.max(a,h.x),o=Math.min(o,h.y),t=Math.max(t,h.y);let d=(e+a)/2,g=(o+t)/2;return[d,g]}function E(n,e){p.fillStyle=`rgb(${Math.round(e.r*255)}, ${Math.round(e.g*255)}, ${Math.round(e.b*255)})`;let a=n[0];p.beginPath(),p.moveTo(a.x*u.width,a.y*u.height);for(let o=1;o<n.length;o++){let t=n[o];p.lineTo(t.x*u.width,t.y*u.height)}p.closePath(),p.fill()}async function H(n){if(!l){console.warn("[Face Plugin] Cannot update mask: faceLandmarker missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");p.clearRect(0,0,u.width,u.height),n.forEach((a,o)=>{E(f.OUTER_LIP.map(t=>a[t]),{r:.25,g:0,b:0}),E(f.INNER_LIP.map(t=>a[t]),{r:.75,g:0,b:0}),E(e.FACE_LANDMARKS_TESSELATION.map(({start:t})=>a[t]),{r:0,g:.25,b:0}),E(e.FACE_LANDMARKS_FACE_OVAL.map(({start:t})=>a[t]),{r:0,g:1,b:0}),E(f.LEFT_EYEBROW.map(t=>a[t]),{r:0,g:0,b:.15}),E(f.RIGHT_EYEBROW.map(t=>a[t]),{r:0,g:0,b:.35}),E(f.LEFT_EYE.map(t=>a[t]),{r:0,g:0,b:.65}),E(f.RIGHT_EYE.map(t=>a[t]),{r:0,g:0,b:.85})}),i.updateTextures({u_faceMask:u})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function L(n){if(!n.faceLandmarks)return;let e=[],a=[],o=[],t=[];for(let g of n.faceLandmarks){let h=Y(g),k=g[f.LEFT_EYE_CENTER],C=g[f.RIGHT_EYE_CENTER],M=g[f.NOSE_TIP];e.push([h[0],1-h[1]]),a.push([k.x,1-k.y]),o.push([C.x,1-C.y]),t.push([M.x,1-M.y])}H(n.faceLandmarks).catch(g=>{console.warn("Mask texture update error:",g)});let d={u_nFaces:n.faceLandmarks.length};n.faceLandmarks.length&&(F.has("u_faceCenter")&&(d.u_faceCenter=e),F.has("u_leftEye")&&(d.u_leftEye=a),F.has("u_rightEye")&&(d.u_rightEye=o),F.has("u_noseTip")&&(d.u_noseTip=t)),i.updateUniforms(d)}i.registerHook("init",async()=>{i.initializeTexture("u_faceMask",u),i.initializeUniform("u_maxFaces","int",m),i.initializeUniform("u_nFaces","int",0);let n=Array.from({length:m},()=>[.5,.5]);i.initializeUniform("u_faceCenter","float",n,{arrayLength:m}),i.initializeUniform("u_leftEye","float",n,{arrayLength:m}),i.initializeUniform("u_rightEye","float",n,{arrayLength:m}),i.initializeUniform("u_noseTip","float",n,{arrayLength:m}),await O()}),i.registerHook("updateTextures",n=>{let e=n[s];if(!(!e||(x.get(s)!==e&&(y=-1),x.set(s,e),!l)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==y){y=e.currentTime;let o=performance.now(),t=l.detectForVideo(e,o);L(t)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let o=l.detect(e);L(o)}}catch(o){console.warn("Face detection error:",o)}}),i.registerHook("destroy",()=>{l&&(l.close(),l=null),b=null,x.clear(),u.remove()}),N(`
1
+ "use strict";var D=Object.create;var T=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var W=(c,s)=>{for(var r in s)T(c,r,{get:s[r],enumerable:!0})},v=(c,s,r,d)=>{if(s&&typeof s=="object"||typeof s=="function")for(let a of $(s))!V.call(c,a)&&a!==r&&T(c,a,{get:()=>s[a],enumerable:!(d=B(s,a))||d.enumerable});return c};var S=(c,s,r)=>(r=c!=null?D(G(c)):{},v(s||!c||!c.__esModule?T(r,"default",{value:c,enumerable:!0}):r,c)),K=c=>v(T({},"__esModule",{value:!0}),c);var q={};W(q,{default:()=>j});module.exports=K(q);var l={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95]};function X(c){let{textureName:s,options:r}=c,d="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(a,w){let{uniforms:_,injectGLSL:O}=w,p=null,b=null,L=-1,y=new Map,u=r?.maxFaces??1,Y=512,z=512,m=document.createElement("canvas");m.width=Y,m.height=z;let E=m.getContext("2d");E.globalCompositeOperation="lighten";async function H(){try{let{FilesetResolver:t,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");b=await t.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),p=await e.createFromOptions(b,{baseOptions:{modelAssetPath:r?.modelPath||d},runningMode:"VIDEO",numFaces:r?.maxFaces??1,minFaceDetectionConfidence:r?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:r?.minFacePresenceConfidence??.5,minTrackingConfidence:r?.minTrackingConfidence??.5,outputFaceBlendshapes:r?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:r?.outputFacialTransformationMatrixes??!1})}catch(t){throw console.error("Failed to initialize Face Landmarker:",t),t}}function x(t){let e=1/0,i=-1/0,o=1/0,n=-1/0;for(let f of t)e=Math.min(e,f.x),i=Math.max(i,f.x),o=Math.min(o,f.y),n=Math.max(n,f.y);let F=(e+i)/2,h=(o+n)/2;return[F,h]}function g(t,e){E.fillStyle=`rgb(${Math.round(e.r*255)}, ${Math.round(e.g*255)}, ${Math.round(e.b*255)})`;let i=t[0];E.beginPath(),E.moveTo(i.x*m.width,i.y*m.height);for(let o=1;o<t.length;o++){let n=t[o];E.lineTo(n.x*m.width,n.y*m.height)}E.closePath(),E.fill()}async function P(t){if(!p){console.warn("[Face Plugin] Cannot update mask: faceLandmarker missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");E.clearRect(0,0,m.width,m.height),t.forEach((i,o)=>{g(l.OUTER_LIP.map(n=>i[n]),{r:.25,g:0,b:0}),g(l.INNER_LIP.map(n=>i[n]),{r:.75,g:0,b:0}),g(e.FACE_LANDMARKS_TESSELATION.map(({start:n})=>i[n]),{r:0,g:.25,b:0}),g(e.FACE_LANDMARKS_FACE_OVAL.map(({start:n})=>i[n]),{r:0,g:1,b:0}),g(l.LEFT_EYEBROW.map(n=>i[n]),{r:0,g:0,b:.15}),g(l.RIGHT_EYEBROW.map(n=>i[n]),{r:0,g:0,b:.35}),g(l.LEFT_EYE.map(n=>i[n]),{r:0,g:0,b:.65}),g(l.RIGHT_EYE.map(n=>i[n]),{r:0,g:0,b:.85})}),a.updateTextures({u_faceMask:m})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function k(t){if(!t.faceLandmarks)return;let e=[],i=[],o=[],n=[],F=[];for(let f of t.faceLandmarks){let C=x(f),M=f[l.LEFT_EYE_CENTER],R=f[l.RIGHT_EYE_CENTER],I=f[l.NOSE_TIP],A=l.INNER_LIP.map(U=>f[U]),N=x(A);e.push([C[0],1-C[1]]),i.push([M.x,1-M.y]),o.push([R.x,1-R.y]),n.push([I.x,1-I.y]),F.push([N[0],1-N[1]])}P(t.faceLandmarks).catch(f=>{console.warn("Mask texture update error:",f)});let h={u_nFaces:t.faceLandmarks.length};t.faceLandmarks.length&&(_.has("u_faceCenter")&&(h.u_faceCenter=e),_.has("u_leftEye")&&(h.u_leftEye=i),_.has("u_rightEye")&&(h.u_rightEye=o),_.has("u_noseTip")&&(h.u_noseTip=n),_.has("u_mouth")&&(h.u_mouth=F)),a.updateUniforms(h)}a.registerHook("init",async()=>{a.initializeTexture("u_faceMask",m),a.initializeUniform("u_maxFaces","int",u),a.initializeUniform("u_nFaces","int",0);let t=Array.from({length:u},()=>[.5,.5]);a.initializeUniform("u_faceCenter","float",t,{arrayLength:u}),a.initializeUniform("u_leftEye","float",t,{arrayLength:u}),a.initializeUniform("u_rightEye","float",t,{arrayLength:u}),a.initializeUniform("u_noseTip","float",t,{arrayLength:u}),a.initializeUniform("u_mouth","float",t,{arrayLength:u}),await H()}),a.registerHook("updateTextures",t=>{let e=t[s];if(!(!e||(y.get(s)!==e&&(L=-1),y.set(s,e),!p)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==L){L=e.currentTime;let o=performance.now(),n=p.detectForVideo(e,o);k(n)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let o=p.detect(e);k(o)}}catch(o){console.warn("Face detection error:",o)}}),a.registerHook("destroy",()=>{p&&(p.close(),p=null),b=null,y.clear(),m.remove()}),O(`
2
2
  uniform int u_maxFaces;
3
3
  uniform int u_nFaces;
4
- uniform vec2 u_faceCenter[${m}];
5
- uniform vec2 u_leftEye[${m}];
6
- uniform vec2 u_rightEye[${m}];
7
- uniform vec2 u_noseTip[${m}];
4
+ uniform vec2 u_faceCenter[${u}];
5
+ uniform vec2 u_leftEye[${u}];
6
+ uniform vec2 u_rightEye[${u}];
7
+ uniform vec2 u_noseTip[${u}];
8
+ uniform vec2 u_mouth[${u}];
8
9
  uniform sampler2D u_faceMask;
9
10
  float getFace(vec2 pos) { return texture(u_faceMask, pos).g; }
10
11
  float getEye(vec2 pos) { return texture(u_faceMask, pos).b; }
11
- float getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`)}}var V=$;
12
+ float getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`)}}var j=X;
12
13
  //# sourceMappingURL=face.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { uniforms, injectGLSL } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateFaceCenter(landmarks: NormalizedLandmark[]): [number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity;\n\n\t\t\t// Calculate bounding box center.\n\t\t\tfor (const landmark of landmarks) {\n\t\t\t\tminX = Math.min(minX, landmark.x);\n\t\t\t\tmaxX = Math.max(maxX, landmark.x);\n\t\t\t\tminY = Math.min(minY, landmark.y);\n\t\t\t\tmaxY = Math.max(maxY, landmark.y);\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\treturn [centerX, centerY];\n\t\t}\n\n\t\tfunction fillRegion(landmarks: NormalizedLandmark[], color: { r: number; g: number; b: number }) {\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tconst origin = landmarks[0];\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tfaceMaskCtx.moveTo(origin.x * faceMaskCanvas.width, origin.y * faceMaskCanvas.height);\n\t\t\tfor (let i = 1; i < landmarks.length; i++) {\n\t\t\t\tconst destination = landmarks[i];\n\t\t\t\tfaceMaskCtx.lineTo(destination.x * faceMaskCanvas.width, destination.y * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!faceLandmarker) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfaces.forEach((landmarks, i) => {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.OUTER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.25, g: 0, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.75, g: 0, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.65 }\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.85 }\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks) return;\n\n\t\t\tconst faceCenters: [number, number][] = [];\n\t\t\tconst leftEyes: [number, number][] = [];\n\t\t\tconst rightEyes: [number, number][] = [];\n\t\t\tconst noseTips: [number, number][] = [];\n\t\t\tfor (const landmarks of result.faceLandmarks) {\n\t\t\t\tconst faceCenter = calculateFaceCenter(landmarks);\n\t\t\t\tconst leftEye = landmarks[LANDMARK_INDICES.LEFT_EYE_CENTER];\n\t\t\t\tconst rightEye = landmarks[LANDMARK_INDICES.RIGHT_EYE_CENTER];\n\t\t\t\tconst noseTip = landmarks[LANDMARK_INDICES.NOSE_TIP];\n\n\t\t\t\t// Invert Y-axis to match WebGL coordinate system.\n\t\t\t\tfaceCenters.push([faceCenter[0], 1.0 - faceCenter[1]]);\n\t\t\t\tleftEyes.push([leftEye.x, 1.0 - leftEye.y]);\n\t\t\t\trightEyes.push([rightEye.x, 1.0 - rightEye.y]);\n\t\t\t\tnoseTips.push([noseTip.x, 1.0 - noseTip.y]);\n\t\t\t}\n\n\t\t\tupdateMaskTexture(result.faceLandmarks).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tconst updates: Parameters<typeof shaderPad.updateUniforms>[0] = { u_nFaces: result.faceLandmarks.length };\n\t\t\tif (result.faceLandmarks.length) {\n\t\t\t\tif (uniforms.has('u_faceCenter')) updates.u_faceCenter = faceCenters;\n\t\t\t\tif (uniforms.has('u_leftEye')) updates.u_leftEye = leftEyes;\n\t\t\t\tif (uniforms.has('u_rightEye')) updates.u_rightEye = rightEyes;\n\t\t\t\tif (uniforms.has('u_noseTip')) updates.u_noseTip = noseTips;\n\t\t\t}\n\t\t\tshaderPad.updateUniforms(updates);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\t\t\tconst defaultFaceData: [number, number][] = Array.from({ length: maxFaces }, () => [0.5, 0.5]);\n\t\t\tshaderPad.initializeUniform('u_faceCenter', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_leftEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_rightEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_noseTip', 'float', defaultFaceData, { arrayLength: maxFaces });\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform vec2 u_faceCenter[${maxFaces}];\nuniform vec2 u_leftEye[${maxFaces}];\nuniform vec2 u_rightEye[${maxFaces}];\nuniform vec2 u_noseTip[${maxFaces}];\nuniform sampler2D u_faceMask;\nfloat getFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat getEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["face_exports","__export","face_default","__toCommonJS","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","uniforms","injectGLSL","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateFaceCenter","landmarks","minX","maxX","minY","maxY","landmark","centerX","centerY","fillRegion","color","origin","i","destination","updateMaskTexture","faces","idx","start","processFaceResults","result","faceCenters","leftEyes","rightEyes","noseTips","faceCenter","leftEye","rightEye","noseTip","updates","defaultFaceData","source","timestamp"]}
1
+ {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { uniforms, injectGLSL } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(landmarks: NormalizedLandmark[]): [number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity;\n\n\t\t\t// Calculate bounding box center.\n\t\t\tfor (const landmark of landmarks) {\n\t\t\t\tminX = Math.min(minX, landmark.x);\n\t\t\t\tmaxX = Math.max(maxX, landmark.x);\n\t\t\t\tminY = Math.min(minY, landmark.y);\n\t\t\t\tmaxY = Math.max(maxY, landmark.y);\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\treturn [centerX, centerY];\n\t\t}\n\n\t\tfunction fillRegion(landmarks: NormalizedLandmark[], color: { r: number; g: number; b: number }) {\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tconst origin = landmarks[0];\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tfaceMaskCtx.moveTo(origin.x * faceMaskCanvas.width, origin.y * faceMaskCanvas.height);\n\t\t\tfor (let i = 1; i < landmarks.length; i++) {\n\t\t\t\tconst destination = landmarks[i];\n\t\t\t\tfaceMaskCtx.lineTo(destination.x * faceMaskCanvas.width, destination.y * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!faceLandmarker) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfaces.forEach((landmarks, i) => {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.OUTER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.25, g: 0, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.75, g: 0, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.65 }\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.85 }\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks) return;\n\n\t\t\tconst faceCenters: [number, number][] = [];\n\t\t\tconst leftEyes: [number, number][] = [];\n\t\t\tconst rightEyes: [number, number][] = [];\n\t\t\tconst noseTips: [number, number][] = [];\n\t\t\tconst mouths: [number, number][] = [];\n\t\t\tfor (const landmarks of result.faceLandmarks) {\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarks);\n\t\t\t\tconst leftEye = landmarks[LANDMARK_INDICES.LEFT_EYE_CENTER];\n\t\t\t\tconst rightEye = landmarks[LANDMARK_INDICES.RIGHT_EYE_CENTER];\n\t\t\t\tconst noseTip = landmarks[LANDMARK_INDICES.NOSE_TIP];\n\t\t\t\tconst innerLipLandmarks = LANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]);\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(innerLipLandmarks);\n\n\t\t\t\t// Invert Y-axis to match WebGL coordinate system.\n\t\t\t\tfaceCenters.push([faceCenter[0], 1.0 - faceCenter[1]]);\n\t\t\t\tleftEyes.push([leftEye.x, 1.0 - leftEye.y]);\n\t\t\t\trightEyes.push([rightEye.x, 1.0 - rightEye.y]);\n\t\t\t\tnoseTips.push([noseTip.x, 1.0 - noseTip.y]);\n\t\t\t\tmouths.push([mouthCenter[0], 1.0 - mouthCenter[1]]);\n\t\t\t}\n\n\t\t\tupdateMaskTexture(result.faceLandmarks).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tconst updates: Parameters<typeof shaderPad.updateUniforms>[0] = { u_nFaces: result.faceLandmarks.length };\n\t\t\tif (result.faceLandmarks.length) {\n\t\t\t\tif (uniforms.has('u_faceCenter')) updates.u_faceCenter = faceCenters;\n\t\t\t\tif (uniforms.has('u_leftEye')) updates.u_leftEye = leftEyes;\n\t\t\t\tif (uniforms.has('u_rightEye')) updates.u_rightEye = rightEyes;\n\t\t\t\tif (uniforms.has('u_noseTip')) updates.u_noseTip = noseTips;\n\t\t\t\tif (uniforms.has('u_mouth')) updates.u_mouth = mouths;\n\t\t\t}\n\t\t\tshaderPad.updateUniforms(updates);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\t\t\tconst defaultFaceData: [number, number][] = Array.from({ length: maxFaces }, () => [0.5, 0.5]);\n\t\t\tshaderPad.initializeUniform('u_faceCenter', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_leftEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_rightEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_noseTip', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_mouth', 'float', defaultFaceData, { arrayLength: maxFaces });\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform vec2 u_faceCenter[${maxFaces}];\nuniform vec2 u_leftEye[${maxFaces}];\nuniform vec2 u_rightEye[${maxFaces}];\nuniform vec2 u_noseTip[${maxFaces}];\nuniform vec2 u_mouth[${maxFaces}];\nuniform sampler2D u_faceMask;\nfloat getFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat getEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["face_exports","__export","face_default","__toCommonJS","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","uniforms","injectGLSL","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","landmarks","minX","maxX","minY","maxY","landmark","centerX","centerY","fillRegion","color","origin","i","destination","updateMaskTexture","faces","idx","start","processFaceResults","result","faceCenters","leftEyes","rightEyes","noseTips","mouths","faceCenter","leftEye","rightEye","noseTip","innerLipLandmarks","mouthCenter","updates","defaultFaceData","source","timestamp"]}
@@ -1,12 +1,13 @@
1
- var s={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95]};function O(k){let{textureName:h,options:u}=k,C="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(r,M){let{uniforms:d,injectGLSL:R}=M,m=null,_=null,F=-1,T=new Map,c=u?.maxFaces??1,I=512,v=512,o=document.createElement("canvas");o.width=I,o.height=v;let f=o.getContext("2d");f.globalCompositeOperation="lighten";async function N(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");_=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),m=await e.createFromOptions(_,{baseOptions:{modelAssetPath:u?.modelPath||C},runningMode:"VIDEO",numFaces:u?.maxFaces??1,minFaceDetectionConfidence:u?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:u?.minFacePresenceConfidence??.5,minTrackingConfidence:u?.minTrackingConfidence??.5,outputFaceBlendshapes:u?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:u?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function S(n){let e=1/0,a=-1/0,i=1/0,t=-1/0;for(let g of n)e=Math.min(e,g.x),a=Math.max(a,g.x),i=Math.min(i,g.y),t=Math.max(t,g.y);let E=(e+a)/2,p=(i+t)/2;return[E,p]}function l(n,e){f.fillStyle=`rgb(${Math.round(e.r*255)}, ${Math.round(e.g*255)}, ${Math.round(e.b*255)})`;let a=n[0];f.beginPath(),f.moveTo(a.x*o.width,a.y*o.height);for(let i=1;i<n.length;i++){let t=n[i];f.lineTo(t.x*o.width,t.y*o.height)}f.closePath(),f.fill()}async function w(n){if(!m){console.warn("[Face Plugin] Cannot update mask: faceLandmarker missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");f.clearRect(0,0,o.width,o.height),n.forEach((a,i)=>{l(s.OUTER_LIP.map(t=>a[t]),{r:.25,g:0,b:0}),l(s.INNER_LIP.map(t=>a[t]),{r:.75,g:0,b:0}),l(e.FACE_LANDMARKS_TESSELATION.map(({start:t})=>a[t]),{r:0,g:.25,b:0}),l(e.FACE_LANDMARKS_FACE_OVAL.map(({start:t})=>a[t]),{r:0,g:1,b:0}),l(s.LEFT_EYEBROW.map(t=>a[t]),{r:0,g:0,b:.15}),l(s.RIGHT_EYEBROW.map(t=>a[t]),{r:0,g:0,b:.35}),l(s.LEFT_EYE.map(t=>a[t]),{r:0,g:0,b:.65}),l(s.RIGHT_EYE.map(t=>a[t]),{r:0,g:0,b:.85})}),r.updateTextures({u_faceMask:o})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function b(n){if(!n.faceLandmarks)return;let e=[],a=[],i=[],t=[];for(let p of n.faceLandmarks){let g=S(p),y=p[s.LEFT_EYE_CENTER],x=p[s.RIGHT_EYE_CENTER],L=p[s.NOSE_TIP];e.push([g[0],1-g[1]]),a.push([y.x,1-y.y]),i.push([x.x,1-x.y]),t.push([L.x,1-L.y])}w(n.faceLandmarks).catch(p=>{console.warn("Mask texture update error:",p)});let E={u_nFaces:n.faceLandmarks.length};n.faceLandmarks.length&&(d.has("u_faceCenter")&&(E.u_faceCenter=e),d.has("u_leftEye")&&(E.u_leftEye=a),d.has("u_rightEye")&&(E.u_rightEye=i),d.has("u_noseTip")&&(E.u_noseTip=t)),r.updateUniforms(E)}r.registerHook("init",async()=>{r.initializeTexture("u_faceMask",o),r.initializeUniform("u_maxFaces","int",c),r.initializeUniform("u_nFaces","int",0);let n=Array.from({length:c},()=>[.5,.5]);r.initializeUniform("u_faceCenter","float",n,{arrayLength:c}),r.initializeUniform("u_leftEye","float",n,{arrayLength:c}),r.initializeUniform("u_rightEye","float",n,{arrayLength:c}),r.initializeUniform("u_noseTip","float",n,{arrayLength:c}),await N()}),r.registerHook("updateTextures",n=>{let e=n[h];if(!(!e||(T.get(h)!==e&&(F=-1),T.set(h,e),!m)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==F){F=e.currentTime;let i=performance.now(),t=m.detectForVideo(e,i);b(t)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let i=m.detect(e);b(i)}}catch(i){console.warn("Face detection error:",i)}}),r.registerHook("destroy",()=>{m&&(m.close(),m=null),_=null,T.clear(),o.remove()}),R(`
1
+ var u={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95]};function P(R){let{textureName:_,options:m}=R,I="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(r,N){let{uniforms:g,injectGLSL:v}=N,f=null,d=null,F=-1,T=new Map,o=m?.maxFaces??1,S=512,w=512,c=document.createElement("canvas");c.width=S,c.height=w;let l=c.getContext("2d");l.globalCompositeOperation="lighten";async function O(){try{let{FilesetResolver:t,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");d=await t.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),f=await e.createFromOptions(d,{baseOptions:{modelAssetPath:m?.modelPath||I},runningMode:"VIDEO",numFaces:m?.maxFaces??1,minFaceDetectionConfidence:m?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:m?.minFacePresenceConfidence??.5,minTrackingConfidence:m?.minTrackingConfidence??.5,outputFaceBlendshapes:m?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:m?.outputFacialTransformationMatrixes??!1})}catch(t){throw console.error("Failed to initialize Face Landmarker:",t),t}}function b(t){let e=1/0,a=-1/0,i=1/0,n=-1/0;for(let s of t)e=Math.min(e,s.x),a=Math.max(a,s.x),i=Math.min(i,s.y),n=Math.max(n,s.y);let h=(e+a)/2,E=(i+n)/2;return[h,E]}function p(t,e){l.fillStyle=`rgb(${Math.round(e.r*255)}, ${Math.round(e.g*255)}, ${Math.round(e.b*255)})`;let a=t[0];l.beginPath(),l.moveTo(a.x*c.width,a.y*c.height);for(let i=1;i<t.length;i++){let n=t[i];l.lineTo(n.x*c.width,n.y*c.height)}l.closePath(),l.fill()}async function Y(t){if(!f){console.warn("[Face Plugin] Cannot update mask: faceLandmarker missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");l.clearRect(0,0,c.width,c.height),t.forEach((a,i)=>{p(u.OUTER_LIP.map(n=>a[n]),{r:.25,g:0,b:0}),p(u.INNER_LIP.map(n=>a[n]),{r:.75,g:0,b:0}),p(e.FACE_LANDMARKS_TESSELATION.map(({start:n})=>a[n]),{r:0,g:.25,b:0}),p(e.FACE_LANDMARKS_FACE_OVAL.map(({start:n})=>a[n]),{r:0,g:1,b:0}),p(u.LEFT_EYEBROW.map(n=>a[n]),{r:0,g:0,b:.15}),p(u.RIGHT_EYEBROW.map(n=>a[n]),{r:0,g:0,b:.35}),p(u.LEFT_EYE.map(n=>a[n]),{r:0,g:0,b:.65}),p(u.RIGHT_EYE.map(n=>a[n]),{r:0,g:0,b:.85})}),r.updateTextures({u_faceMask:c})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function L(t){if(!t.faceLandmarks)return;let e=[],a=[],i=[],n=[],h=[];for(let s of t.faceLandmarks){let y=b(s),x=s[u.LEFT_EYE_CENTER],k=s[u.RIGHT_EYE_CENTER],C=s[u.NOSE_TIP],z=u.INNER_LIP.map(H=>s[H]),M=b(z);e.push([y[0],1-y[1]]),a.push([x.x,1-x.y]),i.push([k.x,1-k.y]),n.push([C.x,1-C.y]),h.push([M[0],1-M[1]])}Y(t.faceLandmarks).catch(s=>{console.warn("Mask texture update error:",s)});let E={u_nFaces:t.faceLandmarks.length};t.faceLandmarks.length&&(g.has("u_faceCenter")&&(E.u_faceCenter=e),g.has("u_leftEye")&&(E.u_leftEye=a),g.has("u_rightEye")&&(E.u_rightEye=i),g.has("u_noseTip")&&(E.u_noseTip=n),g.has("u_mouth")&&(E.u_mouth=h)),r.updateUniforms(E)}r.registerHook("init",async()=>{r.initializeTexture("u_faceMask",c),r.initializeUniform("u_maxFaces","int",o),r.initializeUniform("u_nFaces","int",0);let t=Array.from({length:o},()=>[.5,.5]);r.initializeUniform("u_faceCenter","float",t,{arrayLength:o}),r.initializeUniform("u_leftEye","float",t,{arrayLength:o}),r.initializeUniform("u_rightEye","float",t,{arrayLength:o}),r.initializeUniform("u_noseTip","float",t,{arrayLength:o}),r.initializeUniform("u_mouth","float",t,{arrayLength:o}),await O()}),r.registerHook("updateTextures",t=>{let e=t[_];if(!(!e||(T.get(_)!==e&&(F=-1),T.set(_,e),!f)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==F){F=e.currentTime;let i=performance.now(),n=f.detectForVideo(e,i);L(n)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let i=f.detect(e);L(i)}}catch(i){console.warn("Face detection error:",i)}}),r.registerHook("destroy",()=>{f&&(f.close(),f=null),d=null,T.clear(),c.remove()}),v(`
2
2
  uniform int u_maxFaces;
3
3
  uniform int u_nFaces;
4
- uniform vec2 u_faceCenter[${c}];
5
- uniform vec2 u_leftEye[${c}];
6
- uniform vec2 u_rightEye[${c}];
7
- uniform vec2 u_noseTip[${c}];
4
+ uniform vec2 u_faceCenter[${o}];
5
+ uniform vec2 u_leftEye[${o}];
6
+ uniform vec2 u_rightEye[${o}];
7
+ uniform vec2 u_noseTip[${o}];
8
+ uniform vec2 u_mouth[${o}];
8
9
  uniform sampler2D u_faceMask;
9
10
  float getFace(vec2 pos) { return texture(u_faceMask, pos).g; }
10
11
  float getEye(vec2 pos) { return texture(u_faceMask, pos).b; }
11
- float getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`)}}var Y=O;export{Y as default};
12
+ float getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`)}}var A=P;export{A as default};
12
13
  //# sourceMappingURL=face.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { uniforms, injectGLSL } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateFaceCenter(landmarks: NormalizedLandmark[]): [number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity;\n\n\t\t\t// Calculate bounding box center.\n\t\t\tfor (const landmark of landmarks) {\n\t\t\t\tminX = Math.min(minX, landmark.x);\n\t\t\t\tmaxX = Math.max(maxX, landmark.x);\n\t\t\t\tminY = Math.min(minY, landmark.y);\n\t\t\t\tmaxY = Math.max(maxY, landmark.y);\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\treturn [centerX, centerY];\n\t\t}\n\n\t\tfunction fillRegion(landmarks: NormalizedLandmark[], color: { r: number; g: number; b: number }) {\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tconst origin = landmarks[0];\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tfaceMaskCtx.moveTo(origin.x * faceMaskCanvas.width, origin.y * faceMaskCanvas.height);\n\t\t\tfor (let i = 1; i < landmarks.length; i++) {\n\t\t\t\tconst destination = landmarks[i];\n\t\t\t\tfaceMaskCtx.lineTo(destination.x * faceMaskCanvas.width, destination.y * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!faceLandmarker) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfaces.forEach((landmarks, i) => {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.OUTER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.25, g: 0, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.75, g: 0, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.65 }\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.85 }\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks) return;\n\n\t\t\tconst faceCenters: [number, number][] = [];\n\t\t\tconst leftEyes: [number, number][] = [];\n\t\t\tconst rightEyes: [number, number][] = [];\n\t\t\tconst noseTips: [number, number][] = [];\n\t\t\tfor (const landmarks of result.faceLandmarks) {\n\t\t\t\tconst faceCenter = calculateFaceCenter(landmarks);\n\t\t\t\tconst leftEye = landmarks[LANDMARK_INDICES.LEFT_EYE_CENTER];\n\t\t\t\tconst rightEye = landmarks[LANDMARK_INDICES.RIGHT_EYE_CENTER];\n\t\t\t\tconst noseTip = landmarks[LANDMARK_INDICES.NOSE_TIP];\n\n\t\t\t\t// Invert Y-axis to match WebGL coordinate system.\n\t\t\t\tfaceCenters.push([faceCenter[0], 1.0 - faceCenter[1]]);\n\t\t\t\tleftEyes.push([leftEye.x, 1.0 - leftEye.y]);\n\t\t\t\trightEyes.push([rightEye.x, 1.0 - rightEye.y]);\n\t\t\t\tnoseTips.push([noseTip.x, 1.0 - noseTip.y]);\n\t\t\t}\n\n\t\t\tupdateMaskTexture(result.faceLandmarks).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tconst updates: Parameters<typeof shaderPad.updateUniforms>[0] = { u_nFaces: result.faceLandmarks.length };\n\t\t\tif (result.faceLandmarks.length) {\n\t\t\t\tif (uniforms.has('u_faceCenter')) updates.u_faceCenter = faceCenters;\n\t\t\t\tif (uniforms.has('u_leftEye')) updates.u_leftEye = leftEyes;\n\t\t\t\tif (uniforms.has('u_rightEye')) updates.u_rightEye = rightEyes;\n\t\t\t\tif (uniforms.has('u_noseTip')) updates.u_noseTip = noseTips;\n\t\t\t}\n\t\t\tshaderPad.updateUniforms(updates);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\t\t\tconst defaultFaceData: [number, number][] = Array.from({ length: maxFaces }, () => [0.5, 0.5]);\n\t\t\tshaderPad.initializeUniform('u_faceCenter', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_leftEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_rightEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_noseTip', 'float', defaultFaceData, { arrayLength: maxFaces });\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform vec2 u_faceCenter[${maxFaces}];\nuniform vec2 u_leftEye[${maxFaces}];\nuniform vec2 u_rightEye[${maxFaces}];\nuniform vec2 u_noseTip[${maxFaces}];\nuniform sampler2D u_faceMask;\nfloat getFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat getEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","uniforms","injectGLSL","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateFaceCenter","landmarks","minX","maxX","minY","maxY","landmark","centerX","centerY","fillRegion","color","origin","destination","updateMaskTexture","faces","idx","start","processFaceResults","result","faceCenters","leftEyes","rightEyes","noseTips","faceCenter","leftEye","rightEye","noseTip","updates","defaultFaceData","source","timestamp","face_default"]}
1
+ {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { uniforms, injectGLSL } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(landmarks: NormalizedLandmark[]): [number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity;\n\n\t\t\t// Calculate bounding box center.\n\t\t\tfor (const landmark of landmarks) {\n\t\t\t\tminX = Math.min(minX, landmark.x);\n\t\t\t\tmaxX = Math.max(maxX, landmark.x);\n\t\t\t\tminY = Math.min(minY, landmark.y);\n\t\t\t\tmaxY = Math.max(maxY, landmark.y);\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\treturn [centerX, centerY];\n\t\t}\n\n\t\tfunction fillRegion(landmarks: NormalizedLandmark[], color: { r: number; g: number; b: number }) {\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tconst origin = landmarks[0];\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tfaceMaskCtx.moveTo(origin.x * faceMaskCanvas.width, origin.y * faceMaskCanvas.height);\n\t\t\tfor (let i = 1; i < landmarks.length; i++) {\n\t\t\t\tconst destination = landmarks[i];\n\t\t\t\tfaceMaskCtx.lineTo(destination.x * faceMaskCanvas.width, destination.y * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!faceLandmarker) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfaces.forEach((landmarks, i) => {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.OUTER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.25, g: 0, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0.75, g: 0, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => landmarks[start]),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYEBROW.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.LEFT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.65 }\n\t\t\t\t\t);\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tLANDMARK_INDICES.RIGHT_EYE.map((idx: number) => landmarks[idx]),\n\t\t\t\t\t\t{ r: 0, g: 0, b: 0.85 }\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks) return;\n\n\t\t\tconst faceCenters: [number, number][] = [];\n\t\t\tconst leftEyes: [number, number][] = [];\n\t\t\tconst rightEyes: [number, number][] = [];\n\t\t\tconst noseTips: [number, number][] = [];\n\t\t\tconst mouths: [number, number][] = [];\n\t\t\tfor (const landmarks of result.faceLandmarks) {\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarks);\n\t\t\t\tconst leftEye = landmarks[LANDMARK_INDICES.LEFT_EYE_CENTER];\n\t\t\t\tconst rightEye = landmarks[LANDMARK_INDICES.RIGHT_EYE_CENTER];\n\t\t\t\tconst noseTip = landmarks[LANDMARK_INDICES.NOSE_TIP];\n\t\t\t\tconst innerLipLandmarks = LANDMARK_INDICES.INNER_LIP.map((idx: number) => landmarks[idx]);\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(innerLipLandmarks);\n\n\t\t\t\t// Invert Y-axis to match WebGL coordinate system.\n\t\t\t\tfaceCenters.push([faceCenter[0], 1.0 - faceCenter[1]]);\n\t\t\t\tleftEyes.push([leftEye.x, 1.0 - leftEye.y]);\n\t\t\t\trightEyes.push([rightEye.x, 1.0 - rightEye.y]);\n\t\t\t\tnoseTips.push([noseTip.x, 1.0 - noseTip.y]);\n\t\t\t\tmouths.push([mouthCenter[0], 1.0 - mouthCenter[1]]);\n\t\t\t}\n\n\t\t\tupdateMaskTexture(result.faceLandmarks).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tconst updates: Parameters<typeof shaderPad.updateUniforms>[0] = { u_nFaces: result.faceLandmarks.length };\n\t\t\tif (result.faceLandmarks.length) {\n\t\t\t\tif (uniforms.has('u_faceCenter')) updates.u_faceCenter = faceCenters;\n\t\t\t\tif (uniforms.has('u_leftEye')) updates.u_leftEye = leftEyes;\n\t\t\t\tif (uniforms.has('u_rightEye')) updates.u_rightEye = rightEyes;\n\t\t\t\tif (uniforms.has('u_noseTip')) updates.u_noseTip = noseTips;\n\t\t\t\tif (uniforms.has('u_mouth')) updates.u_mouth = mouths;\n\t\t\t}\n\t\t\tshaderPad.updateUniforms(updates);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\t\t\tconst defaultFaceData: [number, number][] = Array.from({ length: maxFaces }, () => [0.5, 0.5]);\n\t\t\tshaderPad.initializeUniform('u_faceCenter', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_leftEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_rightEye', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_noseTip', 'float', defaultFaceData, { arrayLength: maxFaces });\n\t\t\tshaderPad.initializeUniform('u_mouth', 'float', defaultFaceData, { arrayLength: maxFaces });\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform vec2 u_faceCenter[${maxFaces}];\nuniform vec2 u_leftEye[${maxFaces}];\nuniform vec2 u_rightEye[${maxFaces}];\nuniform vec2 u_noseTip[${maxFaces}];\nuniform vec2 u_mouth[${maxFaces}];\nuniform sampler2D u_faceMask;\nfloat getFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat getEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat getMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","uniforms","injectGLSL","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","landmarks","minX","maxX","minY","maxY","landmark","centerX","centerY","fillRegion","color","origin","destination","updateMaskTexture","faces","idx","start","processFaceResults","result","faceCenters","leftEyes","rightEyes","noseTips","mouths","faceCenter","leftEye","rightEye","noseTip","innerLipLandmarks","mouthCenter","updates","defaultFaceData","source","timestamp","face_default"]}
@@ -2,12 +2,12 @@ import ShaderPad, { PluginContext } from '../index.mjs';
2
2
 
3
3
  declare module '../index' {
4
4
  interface ShaderPad {
5
- save(filename: string): Promise<void>;
5
+ save(filename: string, text?: string): Promise<void>;
6
6
  }
7
7
  }
8
8
  declare function save(): (shaderPad: ShaderPad, context: PluginContext) => void;
9
9
  type WithSave<T extends ShaderPad> = T & {
10
- save(filename: string): Promise<void>;
10
+ save(filename: string, text?: string): Promise<void>;
11
11
  };
12
12
 
13
13
  export { type WithSave, save as default };
@@ -2,12 +2,12 @@ import ShaderPad, { PluginContext } from '../index.js';
2
2
 
3
3
  declare module '../index' {
4
4
  interface ShaderPad {
5
- save(filename: string): Promise<void>;
5
+ save(filename: string, text?: string): Promise<void>;
6
6
  }
7
7
  }
8
8
  declare function save(): (shaderPad: ShaderPad, context: PluginContext) => void;
9
9
  type WithSave<T extends ShaderPad> = T & {
10
- save(filename: string): Promise<void>;
10
+ save(filename: string, text?: string): Promise<void>;
11
11
  };
12
12
 
13
13
  export { type WithSave, save as default };
@@ -1,2 +1,2 @@
1
- "use strict";var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var p=(a,e)=>{for(var t in e)s(a,t,{get:e[t],enumerable:!0})},u=(a,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of g(e))!h.call(a,n)&&n!==t&&s(a,n,{get:()=>e[n],enumerable:!(r=l(e,n))||r.enumerable});return a};var w=a=>u(s({},"__esModule",{value:!0}),a);var f={};p(f,{default:()=>P});module.exports=w(f);function v(){return function(a,e){let{gl:t,canvas:r}=e,n=document.createElement("a");a.save=async function(o){if(t.clear(t.COLOR_BUFFER_BIT),t.drawArrays(t.TRIANGLES,0,6),o&&!`${o}`.toLowerCase().endsWith(".png")&&(o=`${o}.png`),o=o||"export.png","ongesturechange"in window)try{let i=await new Promise(c=>r.toBlob(c,"image/png")),d=new File([i],o,{type:i.type});if(navigator.canShare?.({files:[d]})){await navigator.share({files:[d]});return}}catch(i){console.warn("Web Share API failed:",i)}else n.download=o,n.href=r.toDataURL(),n.click()}}}var P=v;
1
+ "use strict";var d=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var u=(a,e)=>{for(var t in e)d(a,t,{get:e[t],enumerable:!0})},w=(a,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of h(e))!p.call(a,n)&&n!==t&&d(a,n,{get:()=>e[n],enumerable:!(o=g(e,n))||o.enumerable});return a};var f=a=>w(d({},"__esModule",{value:!0}),a);var x={};u(x,{default:()=>P});module.exports=f(x);function v(){return function(a,e){let{gl:t,canvas:o}=e,n=document.createElement("a");a.save=async function(r,c){if(t.clear(t.COLOR_BUFFER_BIT),t.drawArrays(t.TRIANGLES,0,6),r&&!`${r}`.toLowerCase().endsWith(".png")&&(r=`${r}.png`),r=r||"export.png","ongesturechange"in window)try{let i=await new Promise(l=>o.toBlob(l,"image/png")),s={files:[new File([i],r,{type:i.type})]};if(c&&(s.text=c),navigator.canShare?.(s)){await navigator.share(s);return}}catch(i){console.warn("Web Share API failed:",i)}else n.download=r,n.href=o.toDataURL(),n.click()}}}var P=v;
2
2
  //# sourceMappingURL=save.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/save.ts"],"sourcesContent":["import ShaderPad, { PluginContext } from '../index';\n\ndeclare module '../index' {\n\tinterface ShaderPad {\n\t\tsave(filename: string): Promise<void>;\n\t}\n}\n\nfunction save() {\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { gl, canvas } = context;\n\t\tconst downloadLink = document.createElement('a');\n\n\t\t(shaderPad as any).save = async function (filename: string) {\n\t\t\tgl.clear(gl.COLOR_BUFFER_BIT);\n\t\t\tgl.drawArrays(gl.TRIANGLES, 0, 6);\n\n\t\t\tif (filename && !`${filename}`.toLowerCase().endsWith('.png')) {\n\t\t\t\tfilename = `${filename}.png`;\n\t\t\t}\n\t\t\tfilename = filename || 'export.png';\n\t\t\tif ('ongesturechange' in window) {\n\t\t\t\t// Mobile.\n\t\t\t\ttry {\n\t\t\t\t\tconst blob: Blob = await new Promise(resolve =>\n\t\t\t\t\t\tcanvas.toBlob(resolve as BlobCallback, 'image/png')\n\t\t\t\t\t);\n\t\t\t\t\tconst file = new File([blob], filename, { type: blob.type });\n\n\t\t\t\t\tif (navigator.canShare?.({ files: [file] })) {\n\t\t\t\t\t\tawait navigator.share({ files: [file] });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn('Web Share API failed:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Desktop.\n\t\t\t\tdownloadLink.download = filename;\n\t\t\t\tdownloadLink.href = canvas.toDataURL();\n\t\t\t\tdownloadLink.click();\n\t\t\t}\n\t\t};\n\t};\n}\n\n// Type helper.\nexport type WithSave<T extends ShaderPad> = T & {\n\tsave(filename: string): Promise<void>;\n};\n\nexport default save;\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAQA,SAASI,GAAO,CACf,OAAO,SAAUC,EAAsBC,EAAwB,CAC9D,GAAM,CAAE,GAAAC,EAAI,OAAAC,CAAO,EAAIF,EACjBG,EAAe,SAAS,cAAc,GAAG,EAE9CJ,EAAkB,KAAO,eAAgBK,EAAkB,CAQ3D,GAPAH,EAAG,MAAMA,EAAG,gBAAgB,EAC5BA,EAAG,WAAWA,EAAG,UAAW,EAAG,CAAC,EAE5BG,GAAY,CAAC,GAAGA,CAAQ,GAAG,YAAY,EAAE,SAAS,MAAM,IAC3DA,EAAW,GAAGA,CAAQ,QAEvBA,EAAWA,GAAY,aACnB,oBAAqB,OAExB,GAAI,CACH,IAAMC,EAAa,MAAM,IAAI,QAAQC,GACpCJ,EAAO,OAAOI,EAAyB,WAAW,CACnD,EACMC,EAAO,IAAI,KAAK,CAACF,CAAI,EAAGD,EAAU,CAAE,KAAMC,EAAK,IAAK,CAAC,EAE3D,GAAI,UAAU,WAAW,CAAE,MAAO,CAACE,CAAI,CAAE,CAAC,EAAG,CAC5C,MAAM,UAAU,MAAM,CAAE,MAAO,CAACA,CAAI,CAAE,CAAC,EACvC,MACD,CACD,OAASC,EAAO,CACf,QAAQ,KAAK,wBAAyBA,CAAK,CAC5C,MAGAL,EAAa,SAAWC,EACxBD,EAAa,KAAOD,EAAO,UAAU,EACrCC,EAAa,MAAM,CAErB,CACD,CACD,CAOA,IAAOP,EAAQE","names":["save_exports","__export","save_default","__toCommonJS","save","shaderPad","context","gl","canvas","downloadLink","filename","blob","resolve","file","error"]}
1
+ {"version":3,"sources":["../../src/plugins/save.ts"],"sourcesContent":["import ShaderPad, { PluginContext } from '../index';\n\ndeclare module '../index' {\n\tinterface ShaderPad {\n\t\tsave(filename: string, text?: string): Promise<void>;\n\t}\n}\n\nfunction save() {\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { gl, canvas } = context;\n\t\tconst downloadLink = document.createElement('a');\n\n\t\t(shaderPad as any).save = async function (filename: string, text?: string) {\n\t\t\tgl.clear(gl.COLOR_BUFFER_BIT);\n\t\t\tgl.drawArrays(gl.TRIANGLES, 0, 6);\n\n\t\t\tif (filename && !`${filename}`.toLowerCase().endsWith('.png')) {\n\t\t\t\tfilename = `${filename}.png`;\n\t\t\t}\n\t\t\tfilename = filename || 'export.png';\n\t\t\tif ('ongesturechange' in window) {\n\t\t\t\t// Mobile.\n\t\t\t\ttry {\n\t\t\t\t\tconst blob: Blob = await new Promise(resolve =>\n\t\t\t\t\t\tcanvas.toBlob(resolve as BlobCallback, 'image/png')\n\t\t\t\t\t);\n\t\t\t\t\tconst file = new File([blob], filename, { type: blob.type });\n\n\t\t\t\t\tconst shareData: ShareData = { files: [file] };\n\t\t\t\t\tif (text) shareData.text = text;\n\n\t\t\t\t\tif (navigator.canShare?.(shareData)) {\n\t\t\t\t\t\tawait navigator.share(shareData);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn('Web Share API failed:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Desktop.\n\t\t\t\tdownloadLink.download = filename;\n\t\t\t\tdownloadLink.href = canvas.toDataURL();\n\t\t\t\tdownloadLink.click();\n\t\t\t}\n\t\t};\n\t};\n}\n\n// Type helper.\nexport type WithSave<T extends ShaderPad> = T & {\n\tsave(filename: string, text?: string): Promise<void>;\n};\n\nexport default save;\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAQA,SAASI,GAAO,CACf,OAAO,SAAUC,EAAsBC,EAAwB,CAC9D,GAAM,CAAE,GAAAC,EAAI,OAAAC,CAAO,EAAIF,EACjBG,EAAe,SAAS,cAAc,GAAG,EAE9CJ,EAAkB,KAAO,eAAgBK,EAAkBC,EAAe,CAQ1E,GAPAJ,EAAG,MAAMA,EAAG,gBAAgB,EAC5BA,EAAG,WAAWA,EAAG,UAAW,EAAG,CAAC,EAE5BG,GAAY,CAAC,GAAGA,CAAQ,GAAG,YAAY,EAAE,SAAS,MAAM,IAC3DA,EAAW,GAAGA,CAAQ,QAEvBA,EAAWA,GAAY,aACnB,oBAAqB,OAExB,GAAI,CACH,IAAME,EAAa,MAAM,IAAI,QAAQC,GACpCL,EAAO,OAAOK,EAAyB,WAAW,CACnD,EAGMC,EAAuB,CAAE,MAAO,CAFzB,IAAI,KAAK,CAACF,CAAI,EAAGF,EAAU,CAAE,KAAME,EAAK,IAAK,CAAC,CAEhB,CAAE,EAG7C,GAFID,IAAMG,EAAU,KAAOH,GAEvB,UAAU,WAAWG,CAAS,EAAG,CACpC,MAAM,UAAU,MAAMA,CAAS,EAC/B,MACD,CACD,OAASC,EAAO,CACf,QAAQ,KAAK,wBAAyBA,CAAK,CAC5C,MAGAN,EAAa,SAAWC,EACxBD,EAAa,KAAOD,EAAO,UAAU,EACrCC,EAAa,MAAM,CAErB,CACD,CACD,CAOA,IAAOP,EAAQE","names":["save_exports","__export","save_default","__toCommonJS","save","shaderPad","context","gl","canvas","downloadLink","filename","text","blob","resolve","shareData","error"]}
@@ -1,2 +1,2 @@
1
- function c(){return function(i,s){let{gl:a,canvas:o}=s,n=document.createElement("a");i.save=async function(e){if(a.clear(a.COLOR_BUFFER_BIT),a.drawArrays(a.TRIANGLES,0,6),e&&!`${e}`.toLowerCase().endsWith(".png")&&(e=`${e}.png`),e=e||"export.png","ongesturechange"in window)try{let t=await new Promise(d=>o.toBlob(d,"image/png")),r=new File([t],e,{type:t.type});if(navigator.canShare?.({files:[r]})){await navigator.share({files:[r]});return}}catch(t){console.warn("Web Share API failed:",t)}else n.download=e,n.href=o.toDataURL(),n.click()}}}var l=c;export{l as default};
1
+ function l(){return function(s,d){let{gl:a,canvas:o}=d,n=document.createElement("a");s.save=async function(e,i){if(a.clear(a.COLOR_BUFFER_BIT),a.drawArrays(a.TRIANGLES,0,6),e&&!`${e}`.toLowerCase().endsWith(".png")&&(e=`${e}.png`),e=e||"export.png","ongesturechange"in window)try{let t=await new Promise(c=>o.toBlob(c,"image/png")),r={files:[new File([t],e,{type:t.type})]};if(i&&(r.text=i),navigator.canShare?.(r)){await navigator.share(r);return}}catch(t){console.warn("Web Share API failed:",t)}else n.download=e,n.href=o.toDataURL(),n.click()}}}var h=l;export{h as default};
2
2
  //# sourceMappingURL=save.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/save.ts"],"sourcesContent":["import ShaderPad, { PluginContext } from '../index';\n\ndeclare module '../index' {\n\tinterface ShaderPad {\n\t\tsave(filename: string): Promise<void>;\n\t}\n}\n\nfunction save() {\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { gl, canvas } = context;\n\t\tconst downloadLink = document.createElement('a');\n\n\t\t(shaderPad as any).save = async function (filename: string) {\n\t\t\tgl.clear(gl.COLOR_BUFFER_BIT);\n\t\t\tgl.drawArrays(gl.TRIANGLES, 0, 6);\n\n\t\t\tif (filename && !`${filename}`.toLowerCase().endsWith('.png')) {\n\t\t\t\tfilename = `${filename}.png`;\n\t\t\t}\n\t\t\tfilename = filename || 'export.png';\n\t\t\tif ('ongesturechange' in window) {\n\t\t\t\t// Mobile.\n\t\t\t\ttry {\n\t\t\t\t\tconst blob: Blob = await new Promise(resolve =>\n\t\t\t\t\t\tcanvas.toBlob(resolve as BlobCallback, 'image/png')\n\t\t\t\t\t);\n\t\t\t\t\tconst file = new File([blob], filename, { type: blob.type });\n\n\t\t\t\t\tif (navigator.canShare?.({ files: [file] })) {\n\t\t\t\t\t\tawait navigator.share({ files: [file] });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn('Web Share API failed:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Desktop.\n\t\t\t\tdownloadLink.download = filename;\n\t\t\t\tdownloadLink.href = canvas.toDataURL();\n\t\t\t\tdownloadLink.click();\n\t\t\t}\n\t\t};\n\t};\n}\n\n// Type helper.\nexport type WithSave<T extends ShaderPad> = T & {\n\tsave(filename: string): Promise<void>;\n};\n\nexport default save;\n"],"mappings":"AAQA,SAASA,GAAO,CACf,OAAO,SAAUC,EAAsBC,EAAwB,CAC9D,GAAM,CAAE,GAAAC,EAAI,OAAAC,CAAO,EAAIF,EACjBG,EAAe,SAAS,cAAc,GAAG,EAE9CJ,EAAkB,KAAO,eAAgBK,EAAkB,CAQ3D,GAPAH,EAAG,MAAMA,EAAG,gBAAgB,EAC5BA,EAAG,WAAWA,EAAG,UAAW,EAAG,CAAC,EAE5BG,GAAY,CAAC,GAAGA,CAAQ,GAAG,YAAY,EAAE,SAAS,MAAM,IAC3DA,EAAW,GAAGA,CAAQ,QAEvBA,EAAWA,GAAY,aACnB,oBAAqB,OAExB,GAAI,CACH,IAAMC,EAAa,MAAM,IAAI,QAAQC,GACpCJ,EAAO,OAAOI,EAAyB,WAAW,CACnD,EACMC,EAAO,IAAI,KAAK,CAACF,CAAI,EAAGD,EAAU,CAAE,KAAMC,EAAK,IAAK,CAAC,EAE3D,GAAI,UAAU,WAAW,CAAE,MAAO,CAACE,CAAI,CAAE,CAAC,EAAG,CAC5C,MAAM,UAAU,MAAM,CAAE,MAAO,CAACA,CAAI,CAAE,CAAC,EACvC,MACD,CACD,OAASC,EAAO,CACf,QAAQ,KAAK,wBAAyBA,CAAK,CAC5C,MAGAL,EAAa,SAAWC,EACxBD,EAAa,KAAOD,EAAO,UAAU,EACrCC,EAAa,MAAM,CAErB,CACD,CACD,CAOA,IAAOM,EAAQX","names":["save","shaderPad","context","gl","canvas","downloadLink","filename","blob","resolve","file","error","save_default"]}
1
+ {"version":3,"sources":["../../src/plugins/save.ts"],"sourcesContent":["import ShaderPad, { PluginContext } from '../index';\n\ndeclare module '../index' {\n\tinterface ShaderPad {\n\t\tsave(filename: string, text?: string): Promise<void>;\n\t}\n}\n\nfunction save() {\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { gl, canvas } = context;\n\t\tconst downloadLink = document.createElement('a');\n\n\t\t(shaderPad as any).save = async function (filename: string, text?: string) {\n\t\t\tgl.clear(gl.COLOR_BUFFER_BIT);\n\t\t\tgl.drawArrays(gl.TRIANGLES, 0, 6);\n\n\t\t\tif (filename && !`${filename}`.toLowerCase().endsWith('.png')) {\n\t\t\t\tfilename = `${filename}.png`;\n\t\t\t}\n\t\t\tfilename = filename || 'export.png';\n\t\t\tif ('ongesturechange' in window) {\n\t\t\t\t// Mobile.\n\t\t\t\ttry {\n\t\t\t\t\tconst blob: Blob = await new Promise(resolve =>\n\t\t\t\t\t\tcanvas.toBlob(resolve as BlobCallback, 'image/png')\n\t\t\t\t\t);\n\t\t\t\t\tconst file = new File([blob], filename, { type: blob.type });\n\n\t\t\t\t\tconst shareData: ShareData = { files: [file] };\n\t\t\t\t\tif (text) shareData.text = text;\n\n\t\t\t\t\tif (navigator.canShare?.(shareData)) {\n\t\t\t\t\t\tawait navigator.share(shareData);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn('Web Share API failed:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Desktop.\n\t\t\t\tdownloadLink.download = filename;\n\t\t\t\tdownloadLink.href = canvas.toDataURL();\n\t\t\t\tdownloadLink.click();\n\t\t\t}\n\t\t};\n\t};\n}\n\n// Type helper.\nexport type WithSave<T extends ShaderPad> = T & {\n\tsave(filename: string, text?: string): Promise<void>;\n};\n\nexport default save;\n"],"mappings":"AAQA,SAASA,GAAO,CACf,OAAO,SAAUC,EAAsBC,EAAwB,CAC9D,GAAM,CAAE,GAAAC,EAAI,OAAAC,CAAO,EAAIF,EACjBG,EAAe,SAAS,cAAc,GAAG,EAE9CJ,EAAkB,KAAO,eAAgBK,EAAkBC,EAAe,CAQ1E,GAPAJ,EAAG,MAAMA,EAAG,gBAAgB,EAC5BA,EAAG,WAAWA,EAAG,UAAW,EAAG,CAAC,EAE5BG,GAAY,CAAC,GAAGA,CAAQ,GAAG,YAAY,EAAE,SAAS,MAAM,IAC3DA,EAAW,GAAGA,CAAQ,QAEvBA,EAAWA,GAAY,aACnB,oBAAqB,OAExB,GAAI,CACH,IAAME,EAAa,MAAM,IAAI,QAAQC,GACpCL,EAAO,OAAOK,EAAyB,WAAW,CACnD,EAGMC,EAAuB,CAAE,MAAO,CAFzB,IAAI,KAAK,CAACF,CAAI,EAAGF,EAAU,CAAE,KAAME,EAAK,IAAK,CAAC,CAEhB,CAAE,EAG7C,GAFID,IAAMG,EAAU,KAAOH,GAEvB,UAAU,WAAWG,CAAS,EAAG,CACpC,MAAM,UAAU,MAAMA,CAAS,EAC/B,MACD,CACD,OAASC,EAAO,CACf,QAAQ,KAAK,wBAAyBA,CAAK,CAC5C,MAGAN,EAAa,SAAWC,EACxBD,EAAa,KAAOD,EAAO,UAAU,EACrCC,EAAa,MAAM,CAErB,CACD,CACD,CAOA,IAAOO,EAAQZ","names":["save","shaderPad","context","gl","canvas","downloadLink","filename","text","blob","resolve","shareData","error","save_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shaderpad",
3
- "version": "1.0.0-beta.26",
3
+ "version": "1.0.0-beta.28",
4
4
  "description": "A lightweight, dependency-free library to reduce boilerplate when writing fragment shaders.",
5
5
  "keywords": [
6
6
  "shaders",