@ekyc_qoobiss/qbs-ect-cmp 1.5.11
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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/assets/animations/face/LookDown.gif +0 -0
- package/dist/assets/animations/face/LookLeft.gif +0 -0
- package/dist/assets/animations/face/LookRight.gif +0 -0
- package/dist/assets/animations/face/LookUp.gif +0 -0
- package/dist/assets/animations/face/TiltLeft.gif +0 -0
- package/dist/assets/animations/face/TiltRight.gif +0 -0
- package/dist/assets/animations/face/general.gif +0 -0
- package/dist/assets/animations/id/present-id.gif +0 -0
- package/dist/assets/animations/id/straighten-id.gif +0 -0
- package/dist/assets/animations/id/tilt-id.gif +0 -0
- package/dist/assets/buletin/metadata.json +1 -0
- package/dist/assets/buletin/model.json +1 -0
- package/dist/assets/buletin/weights.bin +0 -0
- package/dist/assets/buletin-v2/metadata.json +1 -0
- package/dist/assets/buletin-v2/model.json +1 -0
- package/dist/assets/buletin-v2/weights.bin +0 -0
- package/dist/assets/canvas-masks/face_green.svg +8 -0
- package/dist/assets/canvas-masks/face_white.svg +8 -0
- package/dist/assets/canvas-masks/id_green.svg +6 -0
- package/dist/assets/canvas-masks/id_white.svg +6 -0
- package/dist/assets/capture-error/idError.png +0 -0
- package/dist/assets/capture-error/idError.svg +8 -0
- package/dist/assets/capture-error/selfieError.png +0 -0
- package/dist/assets/capture-error/selfieError.svg +11 -0
- package/dist/assets/complete.svg +4 -0
- package/dist/assets/howtos/circle.svg +11 -0
- package/dist/assets/howtos/idscan.svg +293 -0
- package/dist/assets/howtos/liveness.svg +97 -0
- package/dist/assets/landing/device.svg +14 -0
- package/dist/assets/landing/id.svg +3 -0
- package/dist/assets/landing/info.svg +3 -0
- package/dist/assets/landing/validation.svg +16 -0
- package/dist/cjs/agreement-check_12.cjs.entry.js +6143 -0
- package/dist/cjs/index-9ebcada7.js +1487 -0
- package/dist/cjs/index.cjs.js +2 -0
- package/dist/cjs/loader-dots.cjs.entry.js +19 -0
- package/dist/cjs/loader.cjs.js +21 -0
- package/dist/cjs/qbs-ect-cmp.cjs.js +19 -0
- package/dist/collection/assets/canvas-masks/face_green.svg +8 -0
- package/dist/collection/assets/canvas-masks/face_white.svg +8 -0
- package/dist/collection/assets/canvas-masks/id_green.svg +6 -0
- package/dist/collection/assets/canvas-masks/id_white.svg +6 -0
- package/dist/collection/assets/capture-error/idError.svg +8 -0
- package/dist/collection/assets/capture-error/selfieError.svg +11 -0
- package/dist/collection/assets/complete.svg +4 -0
- package/dist/collection/assets/howtos/circle.svg +11 -0
- package/dist/collection/assets/howtos/idscan.svg +293 -0
- package/dist/collection/assets/howtos/liveness.svg +97 -0
- package/dist/collection/assets/landing/device.svg +14 -0
- package/dist/collection/assets/landing/id.svg +3 -0
- package/dist/collection/assets/landing/info.svg +3 -0
- package/dist/collection/assets/landing/validation.svg +16 -0
- package/dist/collection/collection-manifest.json +24 -0
- package/dist/collection/components/agreement-check/agreement-check.css +0 -0
- package/dist/collection/components/agreement-check/agreement-check.js +75 -0
- package/dist/collection/components/agreement-info/agreement-info.css +0 -0
- package/dist/collection/components/agreement-info/agreement-info.js +72 -0
- package/dist/collection/components/capture-error/capture-error.css +0 -0
- package/dist/collection/components/capture-error/capture-error.js +107 -0
- package/dist/collection/components/controls/camera/camera.css +43 -0
- package/dist/collection/components/controls/camera/camera.js +306 -0
- package/dist/collection/components/controls/loader-dots/loader-dots.css +61 -0
- package/dist/collection/components/controls/loader-dots/loader-dots.js +18 -0
- package/dist/collection/components/end-redirect/end-redirect.css +128 -0
- package/dist/collection/components/end-redirect/end-redirect.js +25 -0
- package/dist/collection/components/how-to-info/how-to-info.css +0 -0
- package/dist/collection/components/how-to-info/how-to-info.js +105 -0
- package/dist/collection/components/id-back-capture/id-back-capture.css +35 -0
- package/dist/collection/components/id-back-capture/id-back-capture.js +201 -0
- package/dist/collection/components/id-capture/id-capture.css +35 -0
- package/dist/collection/components/id-capture/id-capture.js +201 -0
- package/dist/collection/components/identification-component/identification-component.css +999 -0
- package/dist/collection/components/identification-component/identification-component.js +397 -0
- package/dist/collection/components/landing-validation/landing-validation.css +0 -0
- package/dist/collection/components/landing-validation/landing-validation.js +78 -0
- package/dist/collection/components/selfie-capture/selfie-capture.css +22 -0
- package/dist/collection/components/selfie-capture/selfie-capture.js +198 -0
- package/dist/collection/components/sms-code-validation/sms-code-validation.css +0 -0
- package/dist/collection/components/sms-code-validation/sms-code-validation.js +91 -0
- package/dist/collection/global.js +0 -0
- package/dist/collection/helpers/ApiCall.js +153 -0
- package/dist/collection/helpers/Cameras.js +98 -0
- package/dist/collection/helpers/Events.js +79 -0
- package/dist/collection/helpers/ML5.js +20 -0
- package/dist/collection/helpers/Stream.js +223 -0
- package/dist/collection/helpers/canvas.js +10 -0
- package/dist/collection/helpers/index.js +54 -0
- package/dist/collection/helpers/security.js +25 -0
- package/dist/collection/helpers/store.js +15 -0
- package/dist/collection/helpers/textValues.js +82 -0
- package/dist/collection/index.js +1 -0
- package/dist/collection/libs/FaceML5Detector/FaceML5Detector.js +206 -0
- package/dist/collection/libs/FaceML5Detector/FacePose.js +84 -0
- package/dist/collection/libs/IDML5Detector/IDML5Detector.js +110 -0
- package/dist/collection/libs/IDML5Detector/IDPose.js +5 -0
- package/dist/collection/models/ICamera.js +1 -0
- package/dist/collection/models/IConstraints.js +1 -0
- package/dist/collection/models/IDevice.js +1 -0
- package/dist/collection/utils/utils.js +10 -0
- package/dist/esm/agreement-check_12.entry.js +6128 -0
- package/dist/esm/index-3fe6775c.js +1457 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/loader-dots.entry.js +15 -0
- package/dist/esm/loader.js +17 -0
- package/dist/esm/polyfills/core-js.js +11 -0
- package/dist/esm/polyfills/css-shim.js +1 -0
- package/dist/esm/polyfills/dom.js +79 -0
- package/dist/esm/polyfills/es5-html-element.js +1 -0
- package/dist/esm/polyfills/index.js +34 -0
- package/dist/esm/polyfills/system.js +6 -0
- package/dist/esm/qbs-ect-cmp.js +17 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/loader/cdn.js +3 -0
- package/dist/loader/index.cjs.js +3 -0
- package/dist/loader/index.d.ts +12 -0
- package/dist/loader/index.es2017.js +3 -0
- package/dist/loader/index.js +4 -0
- package/dist/loader/package.json +11 -0
- package/dist/qbs-ect-cmp/index.esm.js +0 -0
- package/dist/qbs-ect-cmp/p-3ef0bad2.entry.js +1373 -0
- package/dist/qbs-ect-cmp/p-3fa495e4.js +2 -0
- package/dist/qbs-ect-cmp/p-a69bb428.entry.js +1 -0
- package/dist/qbs-ect-cmp/qbs-ect-cmp.css +1 -0
- package/dist/qbs-ect-cmp/qbs-ect-cmp.esm.js +1 -0
- package/dist/types/components/agreement-check/agreement-check.d.ts +11 -0
- package/dist/types/components/agreement-info/agreement-info.d.ts +13 -0
- package/dist/types/components/capture-error/capture-error.d.ts +11 -0
- package/dist/types/components/controls/camera/camera.d.ts +33 -0
- package/dist/types/components/controls/loader-dots/loader-dots.d.ts +3 -0
- package/dist/types/components/end-redirect/end-redirect.d.ts +4 -0
- package/dist/types/components/how-to-info/how-to-info.d.ts +13 -0
- package/dist/types/components/id-back-capture/id-back-capture.d.ts +26 -0
- package/dist/types/components/id-capture/id-capture.d.ts +26 -0
- package/dist/types/components/identification-component/identification-component.d.ts +31 -0
- package/dist/types/components/landing-validation/landing-validation.d.ts +11 -0
- package/dist/types/components/selfie-capture/selfie-capture.d.ts +26 -0
- package/dist/types/components/sms-code-validation/sms-code-validation.d.ts +14 -0
- package/dist/types/components.d.ts +262 -0
- package/dist/types/global.d.ts +0 -0
- package/dist/types/helpers/ApiCall.d.ts +12 -0
- package/dist/types/helpers/Cameras.d.ts +8 -0
- package/dist/types/helpers/Events.d.ts +12 -0
- package/dist/types/helpers/ML5.d.ts +8 -0
- package/dist/types/helpers/Stream.d.ts +58 -0
- package/dist/types/helpers/canvas.d.ts +2 -0
- package/dist/types/helpers/index.d.ts +4 -0
- package/dist/types/helpers/security.d.ts +4 -0
- package/dist/types/helpers/store.d.ts +10 -0
- package/dist/types/helpers/textValues.d.ts +78 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/libs/FaceML5Detector/FaceML5Detector.d.ts +43 -0
- package/dist/types/libs/FaceML5Detector/FacePose.d.ts +37 -0
- package/dist/types/libs/IDML5Detector/IDML5Detector.d.ts +22 -0
- package/dist/types/libs/IDML5Detector/IDPose.d.ts +4 -0
- package/dist/types/models/ICamera.d.ts +10 -0
- package/dist/types/models/IConstraints.d.ts +21 -0
- package/dist/types/models/IDevice.d.ts +11 -0
- package/dist/types/stencil-public-runtime.d.ts +1581 -0
- package/dist/types/utils/utils.d.ts +2 -0
- package/package.json +51 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const initDevice = () => {
|
|
2
|
+
let device = {
|
|
3
|
+
isMobile: false,
|
|
4
|
+
isAndroid: false,
|
|
5
|
+
isLinux: false,
|
|
6
|
+
isMac: false,
|
|
7
|
+
isWin: false,
|
|
8
|
+
isChrome: false,
|
|
9
|
+
isFirefox: false,
|
|
10
|
+
isSafari: false,
|
|
11
|
+
isIos: false,
|
|
12
|
+
};
|
|
13
|
+
device.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
|
14
|
+
device.isAndroid = /Android/i.test(navigator.userAgent);
|
|
15
|
+
device.isIos = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
16
|
+
device.isLinux = /linux/i.test(navigator.platform);
|
|
17
|
+
device.isMac = /mac/i.test(navigator.platform);
|
|
18
|
+
device.isWin = /win/i.test(navigator.platform);
|
|
19
|
+
device.isChrome = /chrome/i.test(navigator.userAgent);
|
|
20
|
+
device.isFirefox = /firefox/i.test(navigator.userAgent);
|
|
21
|
+
device.isSafari = !device.isChrome ? /safari/i.test(navigator.userAgent) : false;
|
|
22
|
+
device.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
|
23
|
+
device.isAndroid = /Android/i.test(navigator.userAgent);
|
|
24
|
+
device.isIos = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
25
|
+
if (!device.isIos) {
|
|
26
|
+
const isIPad = navigator.userAgent.match(/Mac/) && navigator.maxTouchPoints && navigator.maxTouchPoints > 2;
|
|
27
|
+
if (isIPad) {
|
|
28
|
+
device.isIos = true;
|
|
29
|
+
device.isMobile = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return device;
|
|
33
|
+
};
|
|
34
|
+
export const convertToDataUrl = async (file) => {
|
|
35
|
+
return new Promise(async (resolve) => {
|
|
36
|
+
let arr = [];
|
|
37
|
+
for (let i = 0; i < file.length; i++) {
|
|
38
|
+
const fr = new FileReader();
|
|
39
|
+
fr.onload = () => {
|
|
40
|
+
arr.push(fr.result);
|
|
41
|
+
if (arr.length === file.length)
|
|
42
|
+
resolve(arr);
|
|
43
|
+
};
|
|
44
|
+
fr.readAsDataURL(file[i]);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
export const blobToBase64 = (blob) => {
|
|
49
|
+
return new Promise((resolve, _) => {
|
|
50
|
+
const reader = new FileReader();
|
|
51
|
+
reader.onloadend = () => resolve(reader.result);
|
|
52
|
+
reader.readAsDataURL(blob);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { blobToBase64 } from '../utils/utils';
|
|
2
|
+
import * as piexif from 'piexifjs';
|
|
3
|
+
export const addExifInImg = async (blob, track, videoSize) => {
|
|
4
|
+
const base64 = await blobToBase64(blob);
|
|
5
|
+
const exif = piexif.load(base64);
|
|
6
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
7
|
+
const cameraNames = devices.filter(device => device.kind === 'videoinput').map(device => device.label);
|
|
8
|
+
const json = {
|
|
9
|
+
width: videoSize.width,
|
|
10
|
+
height: videoSize.height,
|
|
11
|
+
usedCamera: track.label,
|
|
12
|
+
camerasOnDevice: cameraNames,
|
|
13
|
+
};
|
|
14
|
+
const newExif = {
|
|
15
|
+
'0th': Object.assign(Object.assign({}, exif['0th']), { [piexif.ImageIFD.Model]: json.usedCamera, [piexif.ImageIFD.ImageWidth]: json.width, [piexif.ImageIFD.ImageLength]: json.height }),
|
|
16
|
+
'Exif': Object.assign(Object.assign({}, exif['Exif']), { [piexif.ExifIFD.UserComment]: JSON.stringify(json) }),
|
|
17
|
+
'GPS': Object.assign({}, exif['GPS']),
|
|
18
|
+
'Interop': Object.assign({}, exif['Interop']),
|
|
19
|
+
'1st': Object.assign({}, exif['1st']),
|
|
20
|
+
'thumbnail': exif['thumbnail'],
|
|
21
|
+
};
|
|
22
|
+
const newExifBinary = piexif.dump(newExif);
|
|
23
|
+
const newPhotoData = piexif.insert(newExifBinary, base64);
|
|
24
|
+
return await fetch(newPhotoData).then(res => res.blob());
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createStore } from '@stencil/store';
|
|
2
|
+
import { SessionKeys } from './textValues';
|
|
3
|
+
const { state, onChange } = createStore({
|
|
4
|
+
flowStatus: '',
|
|
5
|
+
requestId: '',
|
|
6
|
+
token: '',
|
|
7
|
+
cameraIds: [],
|
|
8
|
+
cameraId: '',
|
|
9
|
+
hasIdBack: false,
|
|
10
|
+
apiBaseUrl: 'https://apiro.id-kyc.com',
|
|
11
|
+
});
|
|
12
|
+
onChange('flowStatus', value => {
|
|
13
|
+
sessionStorage.setItem(SessionKeys.FlowStatusKey, value);
|
|
14
|
+
});
|
|
15
|
+
export default state;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export class GlobalValues {
|
|
2
|
+
}
|
|
3
|
+
GlobalValues.FooterText = 'Qoobiss eKYC';
|
|
4
|
+
export class HowToValues extends GlobalValues {
|
|
5
|
+
}
|
|
6
|
+
HowToValues.IdTitile = 'Pentru realizarea procesului de identificare este necesară verificarea actului de identitate';
|
|
7
|
+
HowToValues.IdSubTitileFace = 'Prezintă fața actului de identitate';
|
|
8
|
+
HowToValues.IdSubTitileBack = 'Prezintă spatele actului de identitate';
|
|
9
|
+
HowToValues.IdDescriptionR1 = 'Urmărește cu atenție indicațiile de pe ecran.';
|
|
10
|
+
HowToValues.IdDescriptionR2 = 'Ai grijă să nu se formeze în niciun moment reflexii de lumină pe actul de identitate.';
|
|
11
|
+
HowToValues.IdDescriptionR3 = 'Folosește actul de identitate în original! Utilizarea copiilor duce la invalidarea procesului!';
|
|
12
|
+
HowToValues.IdButton = 'Sunt gata!';
|
|
13
|
+
HowToValues.SelfieTitile = 'Trebuie să realizăm o scurtă înregistrare cu tine în fața camerei telefonului sau a calculatorului.';
|
|
14
|
+
HowToValues.SelfieDescriptionR1 = 'Asigură-te că ești într-o zonă cu sufiecientă lumină.';
|
|
15
|
+
HowToValues.SelfieDescriptionR2 = 'Urmărește cu atenție indicațiile de pe ecran.';
|
|
16
|
+
HowToValues.SelfieButton = 'Sunt gata!';
|
|
17
|
+
export class LandingValues extends GlobalValues {
|
|
18
|
+
}
|
|
19
|
+
LandingValues.Title = 'Validarea identității la distanță este o procedură simplă și rapidă';
|
|
20
|
+
LandingValues.Description = 'Asigură-te că ai:';
|
|
21
|
+
LandingValues.IdInfo = 'Actul de identitate, în original';
|
|
22
|
+
LandingValues.DeviceInfo = 'Un telefon mobil sau un calculator cu camera web';
|
|
23
|
+
LandingValues.SmsInfo = 'Acces la un telefon mobil ce permite primirea de mesaje SMS';
|
|
24
|
+
LandingValues.Warning = 'ATENȚIE! În cadrul acestui proces se poate utiliza doar cartea de identitate Românească.';
|
|
25
|
+
LandingValues.WarningMd = 'ATENȚIE! In cadrul acestui proces se pot utiliza doar buletinele de identitate din Republica Moldova.';
|
|
26
|
+
LandingValues.Terms = 'Prin continuarea procesului, confirmi că ai citit termenii de utilizare și ești de acord cu aceștia.';
|
|
27
|
+
LandingValues.Button = 'Am înțeles';
|
|
28
|
+
export class PhoneValidationValues extends GlobalValues {
|
|
29
|
+
}
|
|
30
|
+
PhoneValidationValues.Title = 'Este necesar să validăm numărul tău de telefon';
|
|
31
|
+
PhoneValidationValues.Description = 'Introdu mai jos numarul tau de telefon:';
|
|
32
|
+
PhoneValidationValues.Button = 'Trimite SMS de verificare';
|
|
33
|
+
PhoneValidationValues.Label = 'Numarul tau de telefon';
|
|
34
|
+
export class CodeValidationValues extends GlobalValues {
|
|
35
|
+
}
|
|
36
|
+
CodeValidationValues.Title = 'Introdu codul de validare primit prin sms:';
|
|
37
|
+
CodeValidationValues.Description = ' ';
|
|
38
|
+
CodeValidationValues.Button = 'Validează';
|
|
39
|
+
CodeValidationValues.Error = 'Codul introdus nu este valid. Asigură-te că îl introduci corect';
|
|
40
|
+
export class CompleteValues extends GlobalValues {
|
|
41
|
+
}
|
|
42
|
+
CompleteValues.Title = 'Procesul a fost finalizat.';
|
|
43
|
+
CompleteValues.Description = 'Vei fi redirecționat în câteva momente.';
|
|
44
|
+
CompleteValues.Message = 'Îți mulțumim!';
|
|
45
|
+
export class SessionKeys {
|
|
46
|
+
}
|
|
47
|
+
SessionKeys.FlowStatusKey = 'qbs-ect-flowstatus';
|
|
48
|
+
SessionKeys.RequestIdKey = 'qbs-ect-requestid';
|
|
49
|
+
export class IdCaptureValues extends GlobalValues {
|
|
50
|
+
}
|
|
51
|
+
IdCaptureValues.Button = 'Încerc din nou';
|
|
52
|
+
IdCaptureValues.Title = 'Încadrează actul de identitate în chenarul de pe ecran.';
|
|
53
|
+
IdCaptureValues.Error = 'Procesul a eșuat. Te rugăm să respecți întocmai instrucțiunile de pe ecran. Ai grijă să încadrezi integral actul în chenraul de pe ecran și să nu apară reflexii de lumină pe suprafața acestuia.';
|
|
54
|
+
IdCaptureValues.IDPoseMapping = {
|
|
55
|
+
0: 'Înclină actul de identitate spre spate.',
|
|
56
|
+
1: 'Îndreaptă actul de identitate si reîncadrează-l în chenarul de pe ecran.',
|
|
57
|
+
};
|
|
58
|
+
IdCaptureValues.Loading = 'Datele sunt încărcate';
|
|
59
|
+
export class SelfieCaptureValues extends GlobalValues {
|
|
60
|
+
}
|
|
61
|
+
SelfieCaptureValues.Title = 'Încadrează fața în chenarul de pe ecran.';
|
|
62
|
+
SelfieCaptureValues.FinalTitle = 'Îndreaptă capul și încadrează fața în chenarul de pe ecran.';
|
|
63
|
+
SelfieCaptureValues.Error = 'Procesul a eșuat. Te rugăm să respecți întocmai instrucțiunile de pe ecran.';
|
|
64
|
+
SelfieCaptureValues.Loading = 'Datele sunt încărcate';
|
|
65
|
+
SelfieCaptureValues.FacePoseMapping = {
|
|
66
|
+
0: 'Întoarce capul spre stânga.',
|
|
67
|
+
1: 'Întoarce capul spre dreapta.',
|
|
68
|
+
2: 'Înclină capul spre spate.',
|
|
69
|
+
3: 'Înclină capul în față.',
|
|
70
|
+
4: 'Înclină capul spre stânga.',
|
|
71
|
+
5: 'Înclină capul spre dreapta.',
|
|
72
|
+
};
|
|
73
|
+
export class AgreementInfoValues extends GlobalValues {
|
|
74
|
+
}
|
|
75
|
+
AgreementInfoValues.Title = 'Pentru începerea identificării avem nevoie de acordurile tale:';
|
|
76
|
+
AgreementInfoValues.Button = 'Încep identificarea';
|
|
77
|
+
AgreementInfoValues.Terms = 'Am luat la cunoștință și sunt de acord cu Termenii și condițiile generale de utilizare';
|
|
78
|
+
AgreementInfoValues.Agreement = 'Îmi exprim acordul pentru prelucrarea datelor cu caracter personal';
|
|
79
|
+
export class AgreementCheckValues extends GlobalValues {
|
|
80
|
+
}
|
|
81
|
+
AgreementCheckValues.ButtonYes = 'Sunt de acord';
|
|
82
|
+
AgreementCheckValues.ButtonNo = 'Nu sunt de acord';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { ML5 } from '../../helpers/ML5';
|
|
2
|
+
import { getVideoRatio } from '../../helpers/canvas';
|
|
3
|
+
import { FacePose } from '../FaceML5Detector/FacePose';
|
|
4
|
+
import { FaceLandmarks } from '../FaceML5Detector/FacePose';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import face_white_svg from '../../assets/canvas-masks/face_white.svg';
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import face_green_svg from '../../assets/canvas-masks/face_green.svg';
|
|
9
|
+
export class FaceML5Detector {
|
|
10
|
+
constructor(stream, isMobile) {
|
|
11
|
+
this.MAX_DETECTION = 1000 * 30;
|
|
12
|
+
this.initTime = null;
|
|
13
|
+
this.videoRatio = 1;
|
|
14
|
+
this.validFaceFound = false;
|
|
15
|
+
this.start = null;
|
|
16
|
+
this.frontFace = null;
|
|
17
|
+
this.requestedFacePose = null;
|
|
18
|
+
this.presentedFacePose = null;
|
|
19
|
+
this.validFacePose = false;
|
|
20
|
+
this.validFaceFoundAgain = false;
|
|
21
|
+
this.MAX_NUMBER_FACES = 1;
|
|
22
|
+
this.MIN_FACE_SCORE = 0.65;
|
|
23
|
+
this.MIN_FACE_SIZE_FOR_MOBILE = 200;
|
|
24
|
+
this.MIN_FACE_SIZE_FOR_DESKTOP = 350;
|
|
25
|
+
this.MIN_OCCUPIED_SPACE_FOR_DESKTOP = 15;
|
|
26
|
+
this.MIN_OCCUPIED_SPACE_FOR_MOBILE = 15;
|
|
27
|
+
this.X_OFFSET_FROM_FRAME = 5;
|
|
28
|
+
this.Y_OFFSET_FROM_FRAME = 5;
|
|
29
|
+
this.stream = stream;
|
|
30
|
+
this.ml5 = ML5.getInstance();
|
|
31
|
+
this.isMobile = isMobile;
|
|
32
|
+
}
|
|
33
|
+
static getInstance(stream, isMobile) {
|
|
34
|
+
if (!FaceML5Detector.instance) {
|
|
35
|
+
FaceML5Detector.instance = new FaceML5Detector(stream, isMobile);
|
|
36
|
+
}
|
|
37
|
+
return FaceML5Detector.instance;
|
|
38
|
+
}
|
|
39
|
+
updateHtmlElements(videoElement, canvasElement, _component) {
|
|
40
|
+
this.videoElement = videoElement;
|
|
41
|
+
this.canvasElement = canvasElement;
|
|
42
|
+
// this.component = component;
|
|
43
|
+
}
|
|
44
|
+
initDetector() {
|
|
45
|
+
this.initTime = Date.now();
|
|
46
|
+
this.continue = true;
|
|
47
|
+
this.width = this.videoElement.videoWidth;
|
|
48
|
+
this.height = this.videoElement.videoHeight;
|
|
49
|
+
this.videoRatio = getVideoRatio(this.videoElement);
|
|
50
|
+
this.drawFrame('white');
|
|
51
|
+
this.delay(2000).then(() => this.detectFaces());
|
|
52
|
+
}
|
|
53
|
+
detectFaces() {
|
|
54
|
+
if (this.validFaceFoundAgain) {
|
|
55
|
+
if (this.start == null)
|
|
56
|
+
this.start = Date.now();
|
|
57
|
+
if (Date.now() > this.start + 3000) {
|
|
58
|
+
this.continue = false;
|
|
59
|
+
this.stream.autoCapturing();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (Date.now() - this.initTime >= this.MAX_DETECTION) {
|
|
63
|
+
this.continue = false;
|
|
64
|
+
this.stream.timeElapsed();
|
|
65
|
+
}
|
|
66
|
+
if (this.presentedFacePose != null && !this.validFacePose) {
|
|
67
|
+
this.continue = false;
|
|
68
|
+
this.stream.timeElapsed();
|
|
69
|
+
}
|
|
70
|
+
if (this.continue)
|
|
71
|
+
this.ml5.faceapi.detect(this.videoElement, this.gotResults.bind(this));
|
|
72
|
+
}
|
|
73
|
+
async gotResults(error, results) {
|
|
74
|
+
if (error) {
|
|
75
|
+
alert(error);
|
|
76
|
+
this.stream.timeElapsed();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (results.length > 0) {
|
|
80
|
+
if (this.isSingleFace(results) && this.checkProbability(results) && this.checkFaceSize(results) && this.checkFaceIndent(results)) {
|
|
81
|
+
if (!this.validFaceFound) {
|
|
82
|
+
this.validFaceFound = true;
|
|
83
|
+
this.frontFace = new FaceLandmarks(results[0], this.width, this.height);
|
|
84
|
+
this.stream.stopAnimation();
|
|
85
|
+
await this.drawFrame('green');
|
|
86
|
+
this.requestedFacePose = this.stream.requestFacePose();
|
|
87
|
+
}
|
|
88
|
+
if (this.validFaceFound && !this.validFaceFoundAgain) {
|
|
89
|
+
await this.checkFacePose(results);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
this.detectFaces();
|
|
94
|
+
}
|
|
95
|
+
async drawFrame(color, persistent = false) {
|
|
96
|
+
return new Promise(resolve => {
|
|
97
|
+
const img = new Image();
|
|
98
|
+
if (color == 'green')
|
|
99
|
+
img.src = face_green_svg;
|
|
100
|
+
else
|
|
101
|
+
img.src = face_white_svg;
|
|
102
|
+
img.onload = async () => {
|
|
103
|
+
const canvas = this.canvasElement;
|
|
104
|
+
const canvasContext = canvas.getContext('2d');
|
|
105
|
+
const hRatio = canvas.width / img.width;
|
|
106
|
+
const vRatio = canvas.height / img.height;
|
|
107
|
+
const ratio = Math.min(hRatio, vRatio);
|
|
108
|
+
const portraitOrientation = canvas.width < canvas.height;
|
|
109
|
+
const paddingX = !portraitOrientation ? 40 : 80;
|
|
110
|
+
const paddingY = portraitOrientation ? 160 : 80;
|
|
111
|
+
const centerShift_x = (canvas.width - img.width * ratio) / 2;
|
|
112
|
+
const centerShift_y = (canvas.height - img.height * ratio) / 2;
|
|
113
|
+
canvasContext.drawImage(img, 0, 0, img.width, img.height, centerShift_x + paddingX, centerShift_y + paddingY, img.width * ratio - paddingX * 2, img.height * ratio - paddingY * 2);
|
|
114
|
+
if (color != 'white' && !persistent) {
|
|
115
|
+
await this.delay(1000);
|
|
116
|
+
this.drawFrame('white');
|
|
117
|
+
}
|
|
118
|
+
resolve();
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
delay(time) {
|
|
123
|
+
return new Promise(resolve => setTimeout(resolve, time));
|
|
124
|
+
}
|
|
125
|
+
isSingleFace(results) {
|
|
126
|
+
return results.length == this.MAX_NUMBER_FACES;
|
|
127
|
+
}
|
|
128
|
+
checkProbability(result) {
|
|
129
|
+
return result[0].detection.score > this.MIN_FACE_SCORE;
|
|
130
|
+
}
|
|
131
|
+
checkFaceSize(result) {
|
|
132
|
+
const faceBox = result[0].detection.box;
|
|
133
|
+
const { width, height, area } = faceBox;
|
|
134
|
+
const { imageHeight, imageWidth } = result[0].detection;
|
|
135
|
+
const occupiedSize = 100 / ((imageHeight * imageWidth) / area);
|
|
136
|
+
const minSide = Math.min(width / this.videoRatio, height / this.videoRatio);
|
|
137
|
+
return (minSide > (this.isMobile ? this.MIN_FACE_SIZE_FOR_MOBILE : this.MIN_FACE_SIZE_FOR_DESKTOP) &&
|
|
138
|
+
occupiedSize > (this.isMobile ? this.MIN_OCCUPIED_SPACE_FOR_MOBILE : this.MIN_OCCUPIED_SPACE_FOR_DESKTOP));
|
|
139
|
+
}
|
|
140
|
+
checkFaceIndent(result) {
|
|
141
|
+
const faceBox = result[0].detection.box;
|
|
142
|
+
const { imageHeight, imageWidth } = result[0].detection;
|
|
143
|
+
const { top, left, bottom, right } = faceBox;
|
|
144
|
+
const topIndent = top;
|
|
145
|
+
const leftIndent = left;
|
|
146
|
+
const rightIndent = imageWidth - right;
|
|
147
|
+
const bottomIndent = imageHeight - bottom;
|
|
148
|
+
return !(topIndent < this.Y_OFFSET_FROM_FRAME || leftIndent < this.X_OFFSET_FROM_FRAME || rightIndent < this.X_OFFSET_FROM_FRAME || bottomIndent < this.Y_OFFSET_FROM_FRAME);
|
|
149
|
+
}
|
|
150
|
+
async checkFacePose(results) {
|
|
151
|
+
let face = new FaceLandmarks(results[0], this.width, this.height);
|
|
152
|
+
// this.drawLandmarks(results);
|
|
153
|
+
// this.drawPoints(face);
|
|
154
|
+
if (this.presentedFacePose == null) {
|
|
155
|
+
let ff = this.frontFace;
|
|
156
|
+
// console.log(face.eyesLevel() +'-'+ '');
|
|
157
|
+
// console.log(ff.eyesLevel() +'-'+ '');
|
|
158
|
+
// console.log('-----');
|
|
159
|
+
if (face.leftEyeNoseDistanceX() < ff.leftEyeNoseDistanceX() * 0.6 &&
|
|
160
|
+
face.rightEyeNoseDistanceX() > ff.rightEyeNoseDistanceX() * 1.2 &&
|
|
161
|
+
face.leftEyeNoseDistanceY() > ff.leftEyeNoseDistanceY() * 0.7 &&
|
|
162
|
+
face.leftEyeNoseDistanceY() < ff.leftEyeNoseDistanceY() * 1.3 &&
|
|
163
|
+
face.rightEyeNoseDistanceY() > ff.rightEyeNoseDistanceY() * 0.7 &&
|
|
164
|
+
face.rightEyeNoseDistanceY() < ff.rightEyeNoseDistanceY() * 1.3) {
|
|
165
|
+
this.presentedFacePose = FacePose.LookLeft;
|
|
166
|
+
}
|
|
167
|
+
else if (face.rightEyeNoseDistanceX() < ff.rightEyeNoseDistanceX() * 0.6 &&
|
|
168
|
+
face.leftEyeNoseDistanceX() > ff.leftEyeNoseDistanceX() * 1.2 &&
|
|
169
|
+
face.leftEyeNoseDistanceY() > ff.leftEyeNoseDistanceY() * 0.7 &&
|
|
170
|
+
face.leftEyeNoseDistanceY() < ff.leftEyeNoseDistanceY() * 1.3 &&
|
|
171
|
+
face.rightEyeNoseDistanceY() > ff.rightEyeNoseDistanceY() * 0.7 &&
|
|
172
|
+
face.rightEyeNoseDistanceY() < ff.rightEyeNoseDistanceY() * 1.3) {
|
|
173
|
+
this.presentedFacePose = FacePose.LookRight;
|
|
174
|
+
}
|
|
175
|
+
else if (face.eyesNoseDistance() < ff.eyesNoseDistance() * 0.45) {
|
|
176
|
+
this.presentedFacePose = FacePose.LookUp;
|
|
177
|
+
}
|
|
178
|
+
else if (face.leftEyeEyeBrowDistance() < ff.leftEyeEyeBrowDistance() * 0.8 && face.rightEyeEyeBrowDistance() < ff.rightEyeEyeBrowDistance() * 0.8) {
|
|
179
|
+
this.presentedFacePose = FacePose.LookDown;
|
|
180
|
+
}
|
|
181
|
+
else if (face.eyesLevel() >= this.frontFace.eyesLevel() + this.height * 0.05) {
|
|
182
|
+
this.presentedFacePose = FacePose.TiltLeft;
|
|
183
|
+
}
|
|
184
|
+
else if (face.eyesLevel() <= this.frontFace.eyesLevel() - this.height * 0.05) {
|
|
185
|
+
this.presentedFacePose = FacePose.TiltRight;
|
|
186
|
+
}
|
|
187
|
+
// if (this.presentedFacePose != null)
|
|
188
|
+
// console.log(FacePose[this.presentedFacePose]);
|
|
189
|
+
if (this.presentedFacePose != null && this.presentedFacePose == this.requestedFacePose) {
|
|
190
|
+
this.validFacePose = true;
|
|
191
|
+
this.stream.stopAnimation();
|
|
192
|
+
await this.drawFrame('green');
|
|
193
|
+
this.stream.changeFacePose();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (this.validFacePose && this.requestedFacePose != null) {
|
|
197
|
+
if (face.leftEyeNoseDistanceX() < face.rightEyeNoseDistanceX() * 1.2 &&
|
|
198
|
+
face.leftEyeNoseDistanceX() > face.rightEyeNoseDistanceX() * 0.8 &&
|
|
199
|
+
Math.abs(face.eyesLevel()) < this.height * 0.1) {
|
|
200
|
+
this.validFaceFoundAgain = true;
|
|
201
|
+
this.stream.stopAnimation();
|
|
202
|
+
this.drawFrame('green', true);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export var FacePose;
|
|
2
|
+
(function (FacePose) {
|
|
3
|
+
FacePose[FacePose["LookLeft"] = 0] = "LookLeft";
|
|
4
|
+
FacePose[FacePose["LookRight"] = 1] = "LookRight";
|
|
5
|
+
FacePose[FacePose["LookUp"] = 2] = "LookUp";
|
|
6
|
+
FacePose[FacePose["LookDown"] = 3] = "LookDown";
|
|
7
|
+
FacePose[FacePose["TiltLeft"] = 4] = "TiltLeft";
|
|
8
|
+
FacePose[FacePose["TiltRight"] = 5] = "TiltRight";
|
|
9
|
+
})(FacePose || (FacePose = {}));
|
|
10
|
+
export class FacePosePick {
|
|
11
|
+
static randomEnum(anEnum) {
|
|
12
|
+
const enumValues = Object.keys(anEnum)
|
|
13
|
+
.map(n => Number.parseInt(n))
|
|
14
|
+
.filter(n => !Number.isNaN(n));
|
|
15
|
+
const randomIndex = Math.floor(Math.random() * enumValues.length);
|
|
16
|
+
const randomEnumValue = enumValues[randomIndex];
|
|
17
|
+
return randomEnumValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class FaceLandmarks {
|
|
21
|
+
constructor(result, w, h) {
|
|
22
|
+
this.leftEye = this.centerOfPoints(result.parts.rightEye, w, h);
|
|
23
|
+
this.rightEye = this.centerOfPoints(result.parts.leftEye, w, h);
|
|
24
|
+
this.leftEyeBrow = this.centerOfPoints(result.parts.rightEyeBrow, w, h);
|
|
25
|
+
this.rightEyeBrow = this.centerOfPoints(result.parts.leftEyeBrow, w, h);
|
|
26
|
+
this.mouth = this.centerOfPoints(result.parts.mouth, w, h);
|
|
27
|
+
this.nose = this.centerOfPoints(result.parts.nose, w, h);
|
|
28
|
+
}
|
|
29
|
+
eyesDistance() {
|
|
30
|
+
return Math.round(Math.hypot(this.leftEye.x - this.rightEye.x, this.leftEye.y - this.rightEye.y));
|
|
31
|
+
}
|
|
32
|
+
eyesLevel() {
|
|
33
|
+
return Math.round(this.leftEye.y - this.rightEye.y);
|
|
34
|
+
}
|
|
35
|
+
eyesCenter() {
|
|
36
|
+
var fp = new FacePoint();
|
|
37
|
+
fp.x = Math.round((this.leftEye.x + this.rightEye.x) / 2);
|
|
38
|
+
fp.y = Math.round((this.leftEye.y + this.rightEye.y) / 2);
|
|
39
|
+
return fp;
|
|
40
|
+
}
|
|
41
|
+
leftEyeNoseDistanceX() {
|
|
42
|
+
return Math.abs(this.leftEye.x - this.nose.x);
|
|
43
|
+
}
|
|
44
|
+
rightEyeNoseDistanceX() {
|
|
45
|
+
return Math.abs(this.rightEye.x - this.nose.x);
|
|
46
|
+
}
|
|
47
|
+
leftEyeNoseDistanceY() {
|
|
48
|
+
return Math.abs(this.leftEye.y - this.nose.y);
|
|
49
|
+
}
|
|
50
|
+
rightEyeNoseDistanceY() {
|
|
51
|
+
return Math.abs(this.rightEye.y - this.nose.y);
|
|
52
|
+
}
|
|
53
|
+
eyesNoseDistance() {
|
|
54
|
+
return Math.round(Math.hypot(this.eyesCenter().x - this.nose.x, this.eyesCenter().y - this.nose.y));
|
|
55
|
+
}
|
|
56
|
+
noseMouthDistance() {
|
|
57
|
+
return Math.round(Math.hypot(this.nose.x - this.mouth.x, this.nose.y - this.mouth.y));
|
|
58
|
+
}
|
|
59
|
+
eyesMouthDistance() {
|
|
60
|
+
return Math.round(Math.hypot(this.eyesCenter().x - this.mouth.x, this.eyesCenter().y - this.mouth.y));
|
|
61
|
+
}
|
|
62
|
+
leftEyeEyeBrowDistance() {
|
|
63
|
+
return Math.round(Math.hypot(this.leftEye.x - this.leftEyeBrow.x, this.leftEye.y - this.leftEyeBrow.y));
|
|
64
|
+
}
|
|
65
|
+
rightEyeEyeBrowDistance() {
|
|
66
|
+
return Math.round(Math.hypot(this.rightEye.x - this.rightEyeBrow.x, this.rightEye.y - this.rightEyeBrow.y));
|
|
67
|
+
}
|
|
68
|
+
centerOfPoints(arr, _width, _height) {
|
|
69
|
+
var minX, maxX, minY, maxY;
|
|
70
|
+
for (var i = 0; i < arr.length; i++) {
|
|
71
|
+
minX = arr[i].x < minX || minX == null ? arr[i].x : minX;
|
|
72
|
+
maxX = arr[i].x > maxX || maxX == null ? arr[i].x : maxX;
|
|
73
|
+
minY = arr[i].y < minY || minY == null ? arr[i].y : minY;
|
|
74
|
+
maxY = arr[i].y > maxY || maxY == null ? arr[i].y : maxY;
|
|
75
|
+
}
|
|
76
|
+
var point = [(minX + maxX) / 2, (minY + maxY) / 2];
|
|
77
|
+
var fp = new FacePoint();
|
|
78
|
+
fp.x = Math.round(point[0]);
|
|
79
|
+
fp.y = Math.round(point[1]);
|
|
80
|
+
return fp;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class FacePoint {
|
|
84
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ML5 } from '../../helpers/ML5';
|
|
2
|
+
import { IDPose } from './IDPose';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import id_white_svg from '../../assets/canvas-masks/id_white.svg';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import id_green_svg from '../../assets/canvas-masks/id_green.svg';
|
|
7
|
+
export class IDML5Detector {
|
|
8
|
+
constructor(stream, _isMobile) {
|
|
9
|
+
this.MAX_DETECTION = 1000 * 20;
|
|
10
|
+
this.initTime = null;
|
|
11
|
+
this.full = 0;
|
|
12
|
+
this.tilted = 0;
|
|
13
|
+
this.start = null;
|
|
14
|
+
this.stream = stream;
|
|
15
|
+
this.ml5 = ML5.getInstance();
|
|
16
|
+
}
|
|
17
|
+
static getInstance(stream, isMobile) {
|
|
18
|
+
if (!IDML5Detector.instance) {
|
|
19
|
+
IDML5Detector.instance = new IDML5Detector(stream, isMobile);
|
|
20
|
+
}
|
|
21
|
+
return IDML5Detector.instance;
|
|
22
|
+
}
|
|
23
|
+
updateHtmlElements(videoElement, canvasElement, _component) {
|
|
24
|
+
this.videoElement = videoElement;
|
|
25
|
+
this.canvasElement = canvasElement;
|
|
26
|
+
// this.component = component;
|
|
27
|
+
}
|
|
28
|
+
initDetector() {
|
|
29
|
+
this.initTime = Date.now();
|
|
30
|
+
this.continue = true;
|
|
31
|
+
// this.width = this.videoElement.videoWidth;
|
|
32
|
+
// this.height = this.videoElement.videoHeight;
|
|
33
|
+
this.drawFrame('white');
|
|
34
|
+
this.delay(2000).then(() => this.classifyVideo());
|
|
35
|
+
}
|
|
36
|
+
classifyVideo() {
|
|
37
|
+
if (this.full == 2 && this.tilted == 1) {
|
|
38
|
+
if (this.start == null)
|
|
39
|
+
this.start = Date.now();
|
|
40
|
+
if (Date.now() > this.start + 3000) {
|
|
41
|
+
this.continue = false;
|
|
42
|
+
this.stream.autoCapturing();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (Date.now() - this.initTime >= this.MAX_DETECTION) {
|
|
46
|
+
this.continue = false;
|
|
47
|
+
this.stream.timeElapsed();
|
|
48
|
+
}
|
|
49
|
+
if (this.continue)
|
|
50
|
+
this.ml5.classifier.classify(this.videoElement, this.gotResults.bind(this));
|
|
51
|
+
}
|
|
52
|
+
async gotResults(error, results) {
|
|
53
|
+
if (error) {
|
|
54
|
+
alert(error);
|
|
55
|
+
this.stream.timeElapsed();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (results[0].label == 'full' && results[0].confidence > 0.9) {
|
|
59
|
+
if (this.full == 0) {
|
|
60
|
+
this.full = 1;
|
|
61
|
+
this.stream.stopAnimation();
|
|
62
|
+
await this.drawFrame('green');
|
|
63
|
+
this.stream.changeIDPose(IDPose.Tilted);
|
|
64
|
+
}
|
|
65
|
+
if (this.full == 1 && this.tilted == 1) {
|
|
66
|
+
this.full = 2;
|
|
67
|
+
this.stream.stopAnimation();
|
|
68
|
+
this.drawFrame('green', true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (results[0].label == 'tilted' && results[0].confidence > 0.8) {
|
|
72
|
+
if (this.full == 1 && this.tilted == 0) {
|
|
73
|
+
this.tilted = 1;
|
|
74
|
+
this.stream.stopAnimation();
|
|
75
|
+
await this.drawFrame('green');
|
|
76
|
+
this.stream.changeIDPose(IDPose.Straight);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
this.classifyVideo();
|
|
80
|
+
}
|
|
81
|
+
async drawFrame(color, persistent = false) {
|
|
82
|
+
return new Promise(resolve => {
|
|
83
|
+
const img = new Image();
|
|
84
|
+
if (color == 'green')
|
|
85
|
+
img.src = id_green_svg;
|
|
86
|
+
else
|
|
87
|
+
img.src = id_white_svg;
|
|
88
|
+
img.onload = async () => {
|
|
89
|
+
const canvas = this.canvasElement;
|
|
90
|
+
const canvasContext = canvas.getContext('2d');
|
|
91
|
+
const hRatio = canvas.width / img.width;
|
|
92
|
+
const vRatio = canvas.height / img.height;
|
|
93
|
+
const ratio = Math.min(hRatio, vRatio);
|
|
94
|
+
const paddingX = 75;
|
|
95
|
+
const paddingY = 75;
|
|
96
|
+
const centerShift_x = (canvas.width - img.width * ratio) / 2;
|
|
97
|
+
const centerShift_y = (canvas.height - img.height * ratio) / 2;
|
|
98
|
+
canvasContext.drawImage(img, 0, 0, img.width, img.height, centerShift_x + paddingX, centerShift_y + paddingY, img.width * ratio - paddingX * 2, img.height * ratio - paddingY * 2);
|
|
99
|
+
if (color != 'white' && !persistent) {
|
|
100
|
+
await this.delay(1000);
|
|
101
|
+
this.drawFrame('white');
|
|
102
|
+
}
|
|
103
|
+
resolve();
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
delay(time) {
|
|
108
|
+
return new Promise(resolve => setTimeout(resolve, time));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function format(first, middle, last) {
|
|
2
|
+
return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '');
|
|
3
|
+
}
|
|
4
|
+
export const blobToBase64 = (blob) => {
|
|
5
|
+
return new Promise((resolve, _) => {
|
|
6
|
+
const reader = new FileReader();
|
|
7
|
+
reader.onloadend = () => resolve(reader.result);
|
|
8
|
+
reader.readAsDataURL(blob);
|
|
9
|
+
});
|
|
10
|
+
};
|