@smileid/web-components 10.0.6 → 11.0.1
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 +15 -15
- package/dist/README.md +15 -0
- package/dist/components/README.md +14 -0
- package/dist/components/document/src/README.md +111 -0
- package/dist/components/document/src/document-capture/README.md +90 -0
- package/dist/components/document/src/document-capture-instructions/README.md +56 -0
- package/dist/components/document/src/document-capture-review/README.md +79 -0
- package/dist/components/selfie/README.md +225 -0
- package/dist/components/smart-camera-web/src/README.md +207 -0
- package/dist/domain/camera/src/README.md +38 -0
- package/dist/domain/file-upload/README.md +35 -0
- package/dist/esm/{DocumentCaptureScreens-BjATTDqu.js → DocumentCaptureScreens-DmH2JZDA.js} +3 -3
- package/dist/esm/DocumentCaptureScreens-DmH2JZDA.js.map +1 -0
- package/dist/esm/EndUserConsent-D4fd1ovG.js.map +1 -1
- package/dist/esm/Navigation-CTjK6tLU.js.map +1 -1
- package/dist/esm/PoweredBySmileId-CxbaihMu.js.map +1 -1
- package/dist/esm/SelfieCaptureScreens-DbdN2zNk.js +7901 -0
- package/dist/esm/SelfieCaptureScreens-DbdN2zNk.js.map +1 -0
- package/dist/esm/SignaturePad-C7MtmT8m.js.map +1 -1
- package/dist/esm/TotpConsent-CQU5jQi4.js.map +1 -1
- package/dist/esm/combobox.js.map +1 -1
- package/dist/esm/document.js +1 -1
- package/dist/esm/main.js +2 -2
- package/dist/esm/{package-CZlW6BZn.js → package-bgeQiff6.js} +2 -2
- package/dist/esm/package-bgeQiff6.js.map +1 -0
- package/dist/esm/selfie.js +1 -1
- package/dist/esm/smart-camera-web.js +3 -3
- package/dist/esm/smart-camera-web.js.map +1 -1
- package/dist/esm/styles-BOEZtbuc.js.map +1 -1
- package/dist/package-lock.json +4948 -0
- package/dist/package.json +59 -0
- package/dist/smart-camera-web.js +72 -98
- package/dist/smart-camera-web.js.gz +0 -0
- package/dist/smart-camera-web.js.map +1 -1
- package/dist/src/components/combobox/src/index.js +2 -0
- package/dist/src/components/combobox/src/index.js.map +7 -0
- package/dist/src/components/document/src/index.js +2 -0
- package/dist/src/components/document/src/index.js.map +7 -0
- package/dist/src/components/end-user-consent/src/index.js +14 -0
- package/dist/src/components/end-user-consent/src/index.js.map +7 -0
- package/dist/src/components/selfie/src/index.js +2 -0
- package/dist/src/components/selfie/src/index.js.map +7 -0
- package/dist/src/components/signature-pad/src/index.js +10 -0
- package/dist/src/components/signature-pad/src/index.js.map +7 -0
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js +2 -0
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js.map +7 -0
- package/dist/src/components/totp-consent/src/index.js +14 -0
- package/dist/src/components/totp-consent/src/index.js.map +7 -0
- package/dist/src/index.js.map +7 -0
- package/dist/styles/README.md +3 -0
- package/dist/types/combobox.d.ts +19 -19
- package/dist/types/document.d.ts +19 -19
- package/dist/types/end-user-consent.d.ts +19 -19
- package/dist/types/main.d.ts +24 -20
- package/dist/types/navigation.d.ts +19 -19
- package/dist/types/selfie.d.ts +19 -19
- package/dist/types/signature-pad.d.ts +19 -19
- package/dist/types/smart-camera-web.d.ts +19 -19
- package/dist/types/totp-consent.d.ts +19 -19
- package/lib/components/README.md +14 -14
- package/lib/components/attribution/PoweredBySmileId.js +42 -42
- package/lib/components/camera-permission/CameraPermission.js +139 -139
- package/lib/components/camera-permission/CameraPermission.stories.js +27 -27
- package/lib/components/combobox/src/Combobox.js +589 -589
- package/lib/components/combobox/src/index.js +1 -1
- package/lib/components/document/src/DocumentCaptureScreens.js +410 -409
- package/lib/components/document/src/DocumentCaptureScreens.stories.js +57 -57
- package/lib/components/document/src/README.md +111 -111
- package/lib/components/document/src/document-capture/DocumentCapture.js +760 -760
- package/lib/components/document/src/document-capture/DocumentCapture.stories.js +78 -78
- package/lib/components/document/src/document-capture/README.md +90 -90
- package/lib/components/document/src/document-capture/index.js +3 -3
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +545 -545
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +24 -24
- package/lib/components/document/src/document-capture-instructions/README.md +56 -56
- package/lib/components/document/src/document-capture-instructions/index.js +3 -3
- package/lib/components/document/src/document-capture-review/DocumentCaptureReview.js +360 -360
- package/lib/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +24 -24
- package/lib/components/document/src/document-capture-review/README.md +79 -79
- package/lib/components/document/src/document-capture-review/index.js +3 -3
- package/lib/components/document/src/index.js +3 -3
- package/lib/components/end-user-consent/src/EndUserConsent.js +795 -795
- package/lib/components/end-user-consent/src/EndUserConsent.stories.js +29 -29
- package/lib/components/end-user-consent/src/index.js +4 -4
- package/lib/components/navigation/src/Navigation.js +171 -171
- package/lib/components/navigation/src/Navigation.stories.js +24 -24
- package/lib/components/navigation/src/index.js +3 -3
- package/lib/components/selfie/README.md +225 -225
- package/lib/components/selfie/src/SelfieCaptureScreens.js +420 -431
- package/lib/components/selfie/src/SelfieCaptureScreens.stories.js +29 -29
- package/lib/components/selfie/src/index.js +3 -3
- package/lib/components/selfie/src/selfie-capture/SelfieCapture.js +1099 -1084
- package/lib/components/selfie/src/selfie-capture/SelfieCapture.stories.js +36 -36
- package/lib/components/selfie/src/selfie-capture/index.js +3 -3
- package/lib/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +689 -689
- package/lib/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +23 -23
- package/lib/components/selfie/src/selfie-capture-instructions/index.js +3 -3
- package/lib/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +209 -209
- package/lib/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +24 -24
- package/lib/components/selfie/src/selfie-capture-review/index.js +3 -3
- package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +256 -239
- package/lib/components/selfie/src/selfie-capture-wrapper/index.ts +1 -1
- package/lib/components/selfie/src/smartselfie-capture/OvalProgress.tsx +81 -81
- package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +265 -283
- package/lib/components/selfie/src/smartselfie-capture/components/AlertDisplay.tsx +34 -34
- package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +97 -97
- package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +78 -76
- package/lib/components/selfie/src/smartselfie-capture/components/index.ts +3 -3
- package/lib/components/selfie/src/smartselfie-capture/constants.ts +23 -23
- package/lib/components/selfie/src/smartselfie-capture/hooks/index.ts +2 -2
- package/lib/components/selfie/src/smartselfie-capture/hooks/useCamera.ts +238 -238
- package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +618 -617
- package/lib/components/selfie/src/smartselfie-capture/index.ts +1 -1
- package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +13 -13
- package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +105 -105
- package/lib/components/selfie/src/smartselfie-capture/utils/faceDetection.ts +129 -129
- package/lib/components/selfie/src/smartselfie-capture/utils/imageCapture.ts +64 -64
- package/lib/components/selfie/src/smartselfie-capture/utils/index.ts +4 -4
- package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +77 -77
- package/lib/components/signature-pad/package-lock.json +3009 -3009
- package/lib/components/signature-pad/package.json +30 -30
- package/lib/components/signature-pad/src/SignaturePad.js +484 -484
- package/lib/components/signature-pad/src/SignaturePad.stories.js +32 -32
- package/lib/components/signature-pad/src/index.js +3 -3
- package/lib/components/smart-camera-web/src/README.md +206 -206
- package/lib/components/smart-camera-web/src/SmartCameraWeb.js +305 -305
- package/lib/components/smart-camera-web/src/SmartCameraWeb.stories.js +57 -57
- package/lib/components/totp-consent/src/TotpConsent.js +949 -949
- package/lib/components/totp-consent/src/index.js +4 -4
- package/lib/domain/camera/src/README.md +38 -38
- package/lib/domain/camera/src/SmartCamera.js +109 -109
- package/lib/domain/constants/src/Constants.js +27 -27
- package/lib/domain/file-upload/README.md +35 -35
- package/lib/domain/file-upload/src/SmartFileUpload.js +65 -65
- package/lib/styles/README.md +3 -3
- package/lib/styles/src/styles.js +372 -372
- package/lib/styles/src/typography.js +52 -52
- package/package.json +111 -112
- package/dist/esm/DocumentCaptureScreens-BjATTDqu.js.map +0 -1
- package/dist/esm/SelfieCaptureScreens-UUzZzl1A.js +0 -11361
- package/dist/esm/SelfieCaptureScreens-UUzZzl1A.js.map +0 -1
- package/dist/esm/package-CZlW6BZn.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as SmartSelfieCapture } from './SmartSelfieCapture';
|
|
1
|
+
export { default as SmartSelfieCapture } from './SmartSelfieCapture';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export const MESSAGES = {
|
|
2
|
-
'multiple-faces': 'Ensure only one face is visible',
|
|
3
|
-
'no-face': 'Position your face in the oval',
|
|
4
|
-
'out-of-bounds': 'Position your face in the oval',
|
|
5
|
-
'too-close': 'Move farther away',
|
|
6
|
-
'too-far': 'Move closer',
|
|
7
|
-
'neutral-expression': 'Neutral expression',
|
|
8
|
-
'smile-required': 'Smile!',
|
|
9
|
-
'open-mouth-smile': 'Wider smile - teeth visible',
|
|
10
|
-
initializing: 'Initializing...',
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type MessageKey = keyof typeof MESSAGES;
|
|
1
|
+
export const MESSAGES = {
|
|
2
|
+
'multiple-faces': 'Ensure only one face is visible',
|
|
3
|
+
'no-face': 'Position your face in the oval',
|
|
4
|
+
'out-of-bounds': 'Position your face in the oval',
|
|
5
|
+
'too-close': 'Move farther away',
|
|
6
|
+
'too-far': 'Move closer',
|
|
7
|
+
'neutral-expression': 'Neutral expression',
|
|
8
|
+
'smile-required': 'Smile!',
|
|
9
|
+
'open-mouth-smile': 'Wider smile - teeth visible',
|
|
10
|
+
initializing: 'Initializing...',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type MessageKey = keyof typeof MESSAGES;
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import { DrawingUtils, FaceLandmarker } from '@mediapipe/tasks-vision';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Create a cropped square canvas from video for face detection
|
|
5
|
-
*/
|
|
6
|
-
export const createCroppedVideoFrame = (
|
|
7
|
-
videoElement: HTMLVideoElement,
|
|
8
|
-
): HTMLCanvasElement | null => {
|
|
9
|
-
const canvas = document.createElement('canvas');
|
|
10
|
-
const ctx = canvas.getContext('2d');
|
|
11
|
-
if (!ctx) return null;
|
|
12
|
-
|
|
13
|
-
const sourceWidth = videoElement.videoWidth;
|
|
14
|
-
const sourceHeight = videoElement.videoHeight;
|
|
15
|
-
|
|
16
|
-
const squareSize = Math.min(sourceWidth, sourceHeight);
|
|
17
|
-
const cropX = (sourceWidth - squareSize) / 2;
|
|
18
|
-
const cropY = (sourceHeight - squareSize) / 2;
|
|
19
|
-
|
|
20
|
-
canvas.width = squareSize;
|
|
21
|
-
canvas.height = squareSize;
|
|
22
|
-
|
|
23
|
-
ctx.drawImage(
|
|
24
|
-
videoElement,
|
|
25
|
-
cropX,
|
|
26
|
-
cropY,
|
|
27
|
-
squareSize,
|
|
28
|
-
squareSize,
|
|
29
|
-
0,
|
|
30
|
-
0,
|
|
31
|
-
squareSize,
|
|
32
|
-
squareSize,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return canvas;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Draw face mesh overlay on canvas
|
|
40
|
-
*/
|
|
41
|
-
export const drawFaceMesh = (
|
|
42
|
-
canvas: HTMLCanvasElement,
|
|
43
|
-
landmarks: any[],
|
|
44
|
-
capturesTaken: number,
|
|
45
|
-
smileCheckpoint: number,
|
|
46
|
-
): void => {
|
|
47
|
-
const ctx = canvas.getContext('2d');
|
|
48
|
-
if (!ctx) return;
|
|
49
|
-
|
|
50
|
-
const canvasWidth = canvas.width;
|
|
51
|
-
const canvasHeight = canvas.height;
|
|
52
|
-
|
|
53
|
-
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
54
|
-
const drawingUtils = new DrawingUtils(ctx);
|
|
55
|
-
|
|
56
|
-
// use this if scaling is needed
|
|
57
|
-
// const scaleFactor = Math.sqrt(canvasWidth * canvasHeight) / 500;
|
|
58
|
-
|
|
59
|
-
landmarks.forEach((landmark) => {
|
|
60
|
-
if (!landmark || landmark.length === 0) return;
|
|
61
|
-
|
|
62
|
-
const outlineColor = 'rgba(162, 155, 254,0.4)';
|
|
63
|
-
const lineWidth = 2; // Math.max(1, scaleFactor * 2);
|
|
64
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
|
|
65
|
-
ctx.lineWidth = lineWidth;
|
|
66
|
-
ctx.lineCap = 'round';
|
|
67
|
-
ctx.lineJoin = 'round';
|
|
68
|
-
|
|
69
|
-
drawingUtils.drawLandmarks(landmark, {
|
|
70
|
-
color: 'rgba(9, 132, 227,0.7)',
|
|
71
|
-
lineWidth: 0.5,
|
|
72
|
-
radius: 0.5,
|
|
73
|
-
});
|
|
74
|
-
drawingUtils.drawConnectors(
|
|
75
|
-
landmark,
|
|
76
|
-
FaceLandmarker.FACE_LANDMARKS_FACE_OVAL,
|
|
77
|
-
{
|
|
78
|
-
color: outlineColor,
|
|
79
|
-
lineWidth,
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const isInSmileZone = capturesTaken > 0 && capturesTaken >= smileCheckpoint;
|
|
84
|
-
if (isInSmileZone) {
|
|
85
|
-
drawingUtils.drawConnectors(
|
|
86
|
-
landmark,
|
|
87
|
-
FaceLandmarker.FACE_LANDMARKS_LIPS,
|
|
88
|
-
{
|
|
89
|
-
color: outlineColor,
|
|
90
|
-
lineWidth,
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Clear canvas completely
|
|
99
|
-
*/
|
|
100
|
-
export const clearCanvas = (canvas: HTMLCanvasElement): void => {
|
|
101
|
-
const ctx = canvas.getContext('2d');
|
|
102
|
-
if (ctx) {
|
|
103
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
1
|
+
import { DrawingUtils, FaceLandmarker } from '@mediapipe/tasks-vision';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a cropped square canvas from video for face detection
|
|
5
|
+
*/
|
|
6
|
+
export const createCroppedVideoFrame = (
|
|
7
|
+
videoElement: HTMLVideoElement,
|
|
8
|
+
): HTMLCanvasElement | null => {
|
|
9
|
+
const canvas = document.createElement('canvas');
|
|
10
|
+
const ctx = canvas.getContext('2d');
|
|
11
|
+
if (!ctx) return null;
|
|
12
|
+
|
|
13
|
+
const sourceWidth = videoElement.videoWidth;
|
|
14
|
+
const sourceHeight = videoElement.videoHeight;
|
|
15
|
+
|
|
16
|
+
const squareSize = Math.min(sourceWidth, sourceHeight);
|
|
17
|
+
const cropX = (sourceWidth - squareSize) / 2;
|
|
18
|
+
const cropY = (sourceHeight - squareSize) / 2;
|
|
19
|
+
|
|
20
|
+
canvas.width = squareSize;
|
|
21
|
+
canvas.height = squareSize;
|
|
22
|
+
|
|
23
|
+
ctx.drawImage(
|
|
24
|
+
videoElement,
|
|
25
|
+
cropX,
|
|
26
|
+
cropY,
|
|
27
|
+
squareSize,
|
|
28
|
+
squareSize,
|
|
29
|
+
0,
|
|
30
|
+
0,
|
|
31
|
+
squareSize,
|
|
32
|
+
squareSize,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return canvas;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Draw face mesh overlay on canvas
|
|
40
|
+
*/
|
|
41
|
+
export const drawFaceMesh = (
|
|
42
|
+
canvas: HTMLCanvasElement,
|
|
43
|
+
landmarks: any[],
|
|
44
|
+
capturesTaken: number,
|
|
45
|
+
smileCheckpoint: number,
|
|
46
|
+
): void => {
|
|
47
|
+
const ctx = canvas.getContext('2d');
|
|
48
|
+
if (!ctx) return;
|
|
49
|
+
|
|
50
|
+
const canvasWidth = canvas.width;
|
|
51
|
+
const canvasHeight = canvas.height;
|
|
52
|
+
|
|
53
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
54
|
+
const drawingUtils = new DrawingUtils(ctx);
|
|
55
|
+
|
|
56
|
+
// use this if scaling is needed
|
|
57
|
+
// const scaleFactor = Math.sqrt(canvasWidth * canvasHeight) / 500;
|
|
58
|
+
|
|
59
|
+
landmarks.forEach((landmark) => {
|
|
60
|
+
if (!landmark || landmark.length === 0) return;
|
|
61
|
+
|
|
62
|
+
const outlineColor = 'rgba(162, 155, 254,0.4)';
|
|
63
|
+
const lineWidth = 2; // Math.max(1, scaleFactor * 2);
|
|
64
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
|
|
65
|
+
ctx.lineWidth = lineWidth;
|
|
66
|
+
ctx.lineCap = 'round';
|
|
67
|
+
ctx.lineJoin = 'round';
|
|
68
|
+
|
|
69
|
+
drawingUtils.drawLandmarks(landmark, {
|
|
70
|
+
color: 'rgba(9, 132, 227,0.7)',
|
|
71
|
+
lineWidth: 0.5,
|
|
72
|
+
radius: 0.5,
|
|
73
|
+
});
|
|
74
|
+
drawingUtils.drawConnectors(
|
|
75
|
+
landmark,
|
|
76
|
+
FaceLandmarker.FACE_LANDMARKS_FACE_OVAL,
|
|
77
|
+
{
|
|
78
|
+
color: outlineColor,
|
|
79
|
+
lineWidth,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const isInSmileZone = capturesTaken > 0 && capturesTaken >= smileCheckpoint;
|
|
84
|
+
if (isInSmileZone) {
|
|
85
|
+
drawingUtils.drawConnectors(
|
|
86
|
+
landmark,
|
|
87
|
+
FaceLandmarker.FACE_LANDMARKS_LIPS,
|
|
88
|
+
{
|
|
89
|
+
color: outlineColor,
|
|
90
|
+
lineWidth,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Clear canvas completely
|
|
99
|
+
*/
|
|
100
|
+
export const clearCanvas = (canvas: HTMLCanvasElement): void => {
|
|
101
|
+
const ctx = canvas.getContext('2d');
|
|
102
|
+
if (ctx) {
|
|
103
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calculate the size of a face relative to the video frame
|
|
3
|
-
*/
|
|
4
|
-
export const calculateFaceSize = (landmarks: any): number => {
|
|
5
|
-
if (!landmarks || landmarks.length === 0) return 0;
|
|
6
|
-
|
|
7
|
-
const face = landmarks[0];
|
|
8
|
-
|
|
9
|
-
if (!face || face.length === 0) return 0;
|
|
10
|
-
|
|
11
|
-
// Get bounding box of face landmarks
|
|
12
|
-
let minX = 1;
|
|
13
|
-
let maxX = 0;
|
|
14
|
-
let minY = 1;
|
|
15
|
-
let maxY = 0;
|
|
16
|
-
|
|
17
|
-
face.forEach((landmark: any) => {
|
|
18
|
-
if (
|
|
19
|
-
landmark &&
|
|
20
|
-
typeof landmark.x === 'number' &&
|
|
21
|
-
typeof landmark.y === 'number'
|
|
22
|
-
) {
|
|
23
|
-
minX = Math.min(minX, landmark.x);
|
|
24
|
-
maxX = Math.max(maxX, landmark.x);
|
|
25
|
-
minY = Math.min(minY, landmark.y);
|
|
26
|
-
maxY = Math.max(maxY, landmark.y);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Calculate face size as percentage of video area
|
|
31
|
-
const faceWidth = maxX - minX;
|
|
32
|
-
const faceHeight = maxY - minY;
|
|
33
|
-
const faceSize = Math.max(faceWidth, faceHeight);
|
|
34
|
-
|
|
35
|
-
return faceSize;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check if a face is positioned within the oval bounds
|
|
40
|
-
*/
|
|
41
|
-
export const isFaceInBounds = (
|
|
42
|
-
landmarks: any,
|
|
43
|
-
videoAspectRatio: number,
|
|
44
|
-
): boolean => {
|
|
45
|
-
if (!landmarks || landmarks.length === 0) return false;
|
|
46
|
-
|
|
47
|
-
const face = landmarks[0];
|
|
48
|
-
|
|
49
|
-
let minX = 1;
|
|
50
|
-
let maxX = 0;
|
|
51
|
-
let minY = 1;
|
|
52
|
-
let maxY = 0;
|
|
53
|
-
face.forEach((landmark: any) => {
|
|
54
|
-
minX = Math.min(minX, landmark.x);
|
|
55
|
-
maxX = Math.max(maxX, landmark.x);
|
|
56
|
-
minY = Math.min(minY, landmark.y);
|
|
57
|
-
maxY = Math.max(maxY, landmark.y);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const ovalCenterX = 0.5;
|
|
61
|
-
const ovalCenterY = 0.6;
|
|
62
|
-
|
|
63
|
-
const isLandscape = videoAspectRatio > 1;
|
|
64
|
-
let ovalWidth;
|
|
65
|
-
let ovalHeight;
|
|
66
|
-
if (isLandscape) {
|
|
67
|
-
ovalWidth = 0.4;
|
|
68
|
-
ovalHeight = 0.3;
|
|
69
|
-
} else {
|
|
70
|
-
ovalWidth = 0.35;
|
|
71
|
-
ovalHeight = 0.5;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const faceCenterX = (minX + maxX) / 2;
|
|
75
|
-
const faceCenterY = (minY + maxY) / 2;
|
|
76
|
-
|
|
77
|
-
const centerTolerance = 0.2;
|
|
78
|
-
const centerOvalWidth = ovalWidth * (1 + centerTolerance);
|
|
79
|
-
const centerOvalHeight = ovalHeight * (1 + centerTolerance);
|
|
80
|
-
|
|
81
|
-
const checkPointInCenterOval = (x: number, y: number) => {
|
|
82
|
-
const dx = (x - ovalCenterX) / centerOvalWidth;
|
|
83
|
-
const dy = (y - ovalCenterY) / centerOvalHeight;
|
|
84
|
-
return dx * dx + dy * dy <= 1;
|
|
85
|
-
};
|
|
86
|
-
const centerInBounds = checkPointInCenterOval(faceCenterX, faceCenterY);
|
|
87
|
-
|
|
88
|
-
const toleranceX = 0.2;
|
|
89
|
-
const toleranceY = 0.1;
|
|
90
|
-
const adjustedOvalWidth = ovalWidth * (1 + toleranceX);
|
|
91
|
-
const adjustedOvalHeight = ovalHeight * (1 + toleranceY);
|
|
92
|
-
|
|
93
|
-
const checkPointInExpandedOval = (x: number, y: number) => {
|
|
94
|
-
const dx = (x - ovalCenterX) / adjustedOvalWidth;
|
|
95
|
-
const dy = (y - ovalCenterY) / adjustedOvalHeight;
|
|
96
|
-
return dx * dx + dy * dy <= 1;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const topLeft = checkPointInExpandedOval(minX, minY);
|
|
100
|
-
const topRight = checkPointInExpandedOval(maxX, minY);
|
|
101
|
-
const bottomLeft = checkPointInExpandedOval(minX, maxY);
|
|
102
|
-
const bottomRight = checkPointInExpandedOval(maxX, maxY);
|
|
103
|
-
|
|
104
|
-
return centerInBounds && topLeft && topRight && bottomLeft && bottomRight;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Calculate mouth opening using face landmarks
|
|
109
|
-
*/
|
|
110
|
-
export const calculateMouthOpening = (landmarks: any): number => {
|
|
111
|
-
if (!landmarks || landmarks.length === 0) return 0;
|
|
112
|
-
|
|
113
|
-
const face = landmarks[0];
|
|
114
|
-
if (!face || face.length === 0) return 0;
|
|
115
|
-
|
|
116
|
-
// MediaPipe face landmark indices for mouth
|
|
117
|
-
const upperLipCenter = face[13]; // Upper lip center
|
|
118
|
-
const lowerLipCenter = face[14]; // Lower lip center
|
|
119
|
-
|
|
120
|
-
if (!upperLipCenter || !lowerLipCenter) return 0;
|
|
121
|
-
|
|
122
|
-
const mouthHeight = Math.abs(lowerLipCenter.y - upperLipCenter.y);
|
|
123
|
-
|
|
124
|
-
const faceTop = Math.min(...face.map((p: any) => p.y));
|
|
125
|
-
const faceBottom = Math.max(...face.map((p: any) => p.y));
|
|
126
|
-
const faceHeight = faceBottom - faceTop;
|
|
127
|
-
|
|
128
|
-
return faceHeight > 0 ? mouthHeight / faceHeight : 0;
|
|
129
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the size of a face relative to the video frame
|
|
3
|
+
*/
|
|
4
|
+
export const calculateFaceSize = (landmarks: any): number => {
|
|
5
|
+
if (!landmarks || landmarks.length === 0) return 0;
|
|
6
|
+
|
|
7
|
+
const face = landmarks[0];
|
|
8
|
+
|
|
9
|
+
if (!face || face.length === 0) return 0;
|
|
10
|
+
|
|
11
|
+
// Get bounding box of face landmarks
|
|
12
|
+
let minX = 1;
|
|
13
|
+
let maxX = 0;
|
|
14
|
+
let minY = 1;
|
|
15
|
+
let maxY = 0;
|
|
16
|
+
|
|
17
|
+
face.forEach((landmark: any) => {
|
|
18
|
+
if (
|
|
19
|
+
landmark &&
|
|
20
|
+
typeof landmark.x === 'number' &&
|
|
21
|
+
typeof landmark.y === 'number'
|
|
22
|
+
) {
|
|
23
|
+
minX = Math.min(minX, landmark.x);
|
|
24
|
+
maxX = Math.max(maxX, landmark.x);
|
|
25
|
+
minY = Math.min(minY, landmark.y);
|
|
26
|
+
maxY = Math.max(maxY, landmark.y);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Calculate face size as percentage of video area
|
|
31
|
+
const faceWidth = maxX - minX;
|
|
32
|
+
const faceHeight = maxY - minY;
|
|
33
|
+
const faceSize = Math.max(faceWidth, faceHeight);
|
|
34
|
+
|
|
35
|
+
return faceSize;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a face is positioned within the oval bounds
|
|
40
|
+
*/
|
|
41
|
+
export const isFaceInBounds = (
|
|
42
|
+
landmarks: any,
|
|
43
|
+
videoAspectRatio: number,
|
|
44
|
+
): boolean => {
|
|
45
|
+
if (!landmarks || landmarks.length === 0) return false;
|
|
46
|
+
|
|
47
|
+
const face = landmarks[0];
|
|
48
|
+
|
|
49
|
+
let minX = 1;
|
|
50
|
+
let maxX = 0;
|
|
51
|
+
let minY = 1;
|
|
52
|
+
let maxY = 0;
|
|
53
|
+
face.forEach((landmark: any) => {
|
|
54
|
+
minX = Math.min(minX, landmark.x);
|
|
55
|
+
maxX = Math.max(maxX, landmark.x);
|
|
56
|
+
minY = Math.min(minY, landmark.y);
|
|
57
|
+
maxY = Math.max(maxY, landmark.y);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const ovalCenterX = 0.5;
|
|
61
|
+
const ovalCenterY = 0.6;
|
|
62
|
+
|
|
63
|
+
const isLandscape = videoAspectRatio > 1;
|
|
64
|
+
let ovalWidth;
|
|
65
|
+
let ovalHeight;
|
|
66
|
+
if (isLandscape) {
|
|
67
|
+
ovalWidth = 0.4;
|
|
68
|
+
ovalHeight = 0.3;
|
|
69
|
+
} else {
|
|
70
|
+
ovalWidth = 0.35;
|
|
71
|
+
ovalHeight = 0.5;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const faceCenterX = (minX + maxX) / 2;
|
|
75
|
+
const faceCenterY = (minY + maxY) / 2;
|
|
76
|
+
|
|
77
|
+
const centerTolerance = 0.2;
|
|
78
|
+
const centerOvalWidth = ovalWidth * (1 + centerTolerance);
|
|
79
|
+
const centerOvalHeight = ovalHeight * (1 + centerTolerance);
|
|
80
|
+
|
|
81
|
+
const checkPointInCenterOval = (x: number, y: number) => {
|
|
82
|
+
const dx = (x - ovalCenterX) / centerOvalWidth;
|
|
83
|
+
const dy = (y - ovalCenterY) / centerOvalHeight;
|
|
84
|
+
return dx * dx + dy * dy <= 1;
|
|
85
|
+
};
|
|
86
|
+
const centerInBounds = checkPointInCenterOval(faceCenterX, faceCenterY);
|
|
87
|
+
|
|
88
|
+
const toleranceX = 0.2;
|
|
89
|
+
const toleranceY = 0.1;
|
|
90
|
+
const adjustedOvalWidth = ovalWidth * (1 + toleranceX);
|
|
91
|
+
const adjustedOvalHeight = ovalHeight * (1 + toleranceY);
|
|
92
|
+
|
|
93
|
+
const checkPointInExpandedOval = (x: number, y: number) => {
|
|
94
|
+
const dx = (x - ovalCenterX) / adjustedOvalWidth;
|
|
95
|
+
const dy = (y - ovalCenterY) / adjustedOvalHeight;
|
|
96
|
+
return dx * dx + dy * dy <= 1;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const topLeft = checkPointInExpandedOval(minX, minY);
|
|
100
|
+
const topRight = checkPointInExpandedOval(maxX, minY);
|
|
101
|
+
const bottomLeft = checkPointInExpandedOval(minX, maxY);
|
|
102
|
+
const bottomRight = checkPointInExpandedOval(maxX, maxY);
|
|
103
|
+
|
|
104
|
+
return centerInBounds && topLeft && topRight && bottomLeft && bottomRight;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculate mouth opening using face landmarks
|
|
109
|
+
*/
|
|
110
|
+
export const calculateMouthOpening = (landmarks: any): number => {
|
|
111
|
+
if (!landmarks || landmarks.length === 0) return 0;
|
|
112
|
+
|
|
113
|
+
const face = landmarks[0];
|
|
114
|
+
if (!face || face.length === 0) return 0;
|
|
115
|
+
|
|
116
|
+
// MediaPipe face landmark indices for mouth
|
|
117
|
+
const upperLipCenter = face[13]; // Upper lip center
|
|
118
|
+
const lowerLipCenter = face[14]; // Lower lip center
|
|
119
|
+
|
|
120
|
+
if (!upperLipCenter || !lowerLipCenter) return 0;
|
|
121
|
+
|
|
122
|
+
const mouthHeight = Math.abs(lowerLipCenter.y - upperLipCenter.y);
|
|
123
|
+
|
|
124
|
+
const faceTop = Math.min(...face.map((p: any) => p.y));
|
|
125
|
+
const faceBottom = Math.max(...face.map((p: any) => p.y));
|
|
126
|
+
const faceHeight = faceBottom - faceTop;
|
|
127
|
+
|
|
128
|
+
return faceHeight > 0 ? mouthHeight / faceHeight : 0;
|
|
129
|
+
};
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
export const captureImageFromVideo = (
|
|
2
|
-
videoElement: HTMLVideoElement,
|
|
3
|
-
isReference: boolean = false,
|
|
4
|
-
): string | null => {
|
|
5
|
-
const canvas = document.createElement('canvas');
|
|
6
|
-
const ctx = canvas.getContext('2d');
|
|
7
|
-
if (!ctx) return null;
|
|
8
|
-
|
|
9
|
-
const isPortrait = videoElement.videoHeight > videoElement.videoWidth;
|
|
10
|
-
|
|
11
|
-
if (isReference) {
|
|
12
|
-
if (isPortrait) {
|
|
13
|
-
canvas.width = 480;
|
|
14
|
-
canvas.height = Math.max(
|
|
15
|
-
640,
|
|
16
|
-
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
17
|
-
);
|
|
18
|
-
} else {
|
|
19
|
-
canvas.width = 640;
|
|
20
|
-
canvas.height = Math.max(
|
|
21
|
-
480,
|
|
22
|
-
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
} else if (isPortrait) {
|
|
26
|
-
canvas.width = 240;
|
|
27
|
-
canvas.height = Math.max(
|
|
28
|
-
320,
|
|
29
|
-
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
30
|
-
);
|
|
31
|
-
} else {
|
|
32
|
-
canvas.width = 320;
|
|
33
|
-
canvas.height = Math.max(
|
|
34
|
-
240,
|
|
35
|
-
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// capture more of the user's head and avoid clipping
|
|
40
|
-
const zoomOutFactor = 1;
|
|
41
|
-
const sourceWidth = videoElement.videoWidth * zoomOutFactor;
|
|
42
|
-
const sourceHeight = videoElement.videoHeight * zoomOutFactor;
|
|
43
|
-
|
|
44
|
-
// center the zoomed out area
|
|
45
|
-
const offsetX = (sourceWidth - videoElement.videoWidth) / 2;
|
|
46
|
-
const offsetY = (sourceHeight - videoElement.videoHeight) / 2;
|
|
47
|
-
|
|
48
|
-
// vertical offset to shift up and capture full head
|
|
49
|
-
const verticalOffset = 0;
|
|
50
|
-
|
|
51
|
-
ctx.drawImage(
|
|
52
|
-
videoElement,
|
|
53
|
-
-offsetX,
|
|
54
|
-
-offsetY - verticalOffset,
|
|
55
|
-
sourceWidth,
|
|
56
|
-
sourceHeight,
|
|
57
|
-
0,
|
|
58
|
-
0,
|
|
59
|
-
canvas.width,
|
|
60
|
-
canvas.height,
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
return canvas.toDataURL('image/jpeg');
|
|
64
|
-
};
|
|
1
|
+
export const captureImageFromVideo = (
|
|
2
|
+
videoElement: HTMLVideoElement,
|
|
3
|
+
isReference: boolean = false,
|
|
4
|
+
): string | null => {
|
|
5
|
+
const canvas = document.createElement('canvas');
|
|
6
|
+
const ctx = canvas.getContext('2d');
|
|
7
|
+
if (!ctx) return null;
|
|
8
|
+
|
|
9
|
+
const isPortrait = videoElement.videoHeight > videoElement.videoWidth;
|
|
10
|
+
|
|
11
|
+
if (isReference) {
|
|
12
|
+
if (isPortrait) {
|
|
13
|
+
canvas.width = 480;
|
|
14
|
+
canvas.height = Math.max(
|
|
15
|
+
640,
|
|
16
|
+
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
17
|
+
);
|
|
18
|
+
} else {
|
|
19
|
+
canvas.width = 640;
|
|
20
|
+
canvas.height = Math.max(
|
|
21
|
+
480,
|
|
22
|
+
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
} else if (isPortrait) {
|
|
26
|
+
canvas.width = 240;
|
|
27
|
+
canvas.height = Math.max(
|
|
28
|
+
320,
|
|
29
|
+
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
canvas.width = 320;
|
|
33
|
+
canvas.height = Math.max(
|
|
34
|
+
240,
|
|
35
|
+
(canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// capture more of the user's head and avoid clipping
|
|
40
|
+
const zoomOutFactor = 1;
|
|
41
|
+
const sourceWidth = videoElement.videoWidth * zoomOutFactor;
|
|
42
|
+
const sourceHeight = videoElement.videoHeight * zoomOutFactor;
|
|
43
|
+
|
|
44
|
+
// center the zoomed out area
|
|
45
|
+
const offsetX = (sourceWidth - videoElement.videoWidth) / 2;
|
|
46
|
+
const offsetY = (sourceHeight - videoElement.videoHeight) / 2;
|
|
47
|
+
|
|
48
|
+
// vertical offset to shift up and capture full head
|
|
49
|
+
const verticalOffset = 0;
|
|
50
|
+
|
|
51
|
+
ctx.drawImage(
|
|
52
|
+
videoElement,
|
|
53
|
+
-offsetX,
|
|
54
|
+
-offsetY - verticalOffset,
|
|
55
|
+
sourceWidth,
|
|
56
|
+
sourceHeight,
|
|
57
|
+
0,
|
|
58
|
+
0,
|
|
59
|
+
canvas.width,
|
|
60
|
+
canvas.height,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return canvas.toDataURL('image/jpeg');
|
|
64
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './faceDetection';
|
|
2
|
-
export * from './canvas';
|
|
3
|
-
export * from './imageCapture';
|
|
4
|
-
export * from './alertMessages';
|
|
1
|
+
export * from './faceDetection';
|
|
2
|
+
export * from './canvas';
|
|
3
|
+
export * from './imageCapture';
|
|
4
|
+
export * from './alertMessages';
|