UNPKG

kwikid-camera

Version:
1,062 lines (1,051 loc) 143 kB
import { __awaiter, __decorate } from 'tslib'; import * as i0 from '@angular/core'; import { Component, ChangeDetectionStrategy, ViewChild, Input, EventEmitter, Inject, Output, Injectable, SecurityContext, NgModule, forwardRef } from '@angular/core'; import { isEmptyValue, isNotEmptyValue, mergeObjects, checkObjectKeyExists, logMethod, getObjectDeepCopy, isNotEmptyString } from 'kwikid-toolkit'; import * as i1$1 from '@taiga-ui/core'; import { TUI_SANITIZER, TuiAlertService, TuiSvgModule, TuiTooltipModule, TuiHintModule, TuiNotificationModule, TuiButtonModule, TuiDropdownModule } from '@taiga-ui/core'; import { NgDompurifySanitizer } from '@tinkoff/ng-dompurify'; import * as i1 from 'kwikui'; import { KwikUIModule } from 'kwikui'; import { trigger, transition, style, animate } from '@angular/animations'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as RecordRTC from 'recordrtc'; import { Subject, Subscription } from 'rxjs'; import * as i2 from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import * as i2$1 from '@angular/forms'; import { FormsModule, ReactiveFormsModule, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { distinctUntilChanged } from 'rxjs/operators'; /* eslint-disable no-unused-vars */ // Type Attribute var EKwikUIInputCameraType; (function (EKwikUIInputCameraType) { EKwikUIInputCameraType["CAPTURE"] = "capture"; EKwikUIInputCameraType["RECORD"] = "record"; })(EKwikUIInputCameraType || (EKwikUIInputCameraType = {})); const VKwikUIInputCameraType = [ EKwikUIInputCameraType.CAPTURE, EKwikUIInputCameraType.RECORD ]; // Size Attribute var EKwikUIInputCameraSize; (function (EKwikUIInputCameraSize) { EKwikUIInputCameraSize["S"] = "s"; EKwikUIInputCameraSize["M"] = "m"; EKwikUIInputCameraSize["L"] = "l"; })(EKwikUIInputCameraSize || (EKwikUIInputCameraSize = {})); const VKwikUIInputCameraSize = [ EKwikUIInputCameraSize.S, EKwikUIInputCameraSize.M, EKwikUIInputCameraSize.L ]; /* eslint-disable no-unused-vars */ // Button Shape var EKwikIDCameraMaskShape; (function (EKwikIDCameraMaskShape) { EKwikIDCameraMaskShape["CIRCLE"] = "circle"; EKwikIDCameraMaskShape["OVAL"] = "oval"; EKwikIDCameraMaskShape["RECTANGLE"] = "rectangle"; EKwikIDCameraMaskShape["SQUARE"] = "square"; })(EKwikIDCameraMaskShape || (EKwikIDCameraMaskShape = {})); const VKwikIDCameraMaskShape = [ EKwikIDCameraMaskShape.CIRCLE, EKwikIDCameraMaskShape.OVAL, EKwikIDCameraMaskShape.RECTANGLE, EKwikIDCameraMaskShape.SQUARE ]; /* eslint-disable no-unused-vars */ var IRecordingState; (function (IRecordingState) { IRecordingState["NONE"] = "NONE"; IRecordingState["RECORDING"] = "RECORDING"; IRecordingState["PAUSED"] = "PAUSED"; IRecordingState["RECORDED"] = "RECORDED"; })(IRecordingState || (IRecordingState = {})); /* eslint-disable no-unused-vars */ // Mask Shape var EKwikIDCameraViewMaskShape; (function (EKwikIDCameraViewMaskShape) { EKwikIDCameraViewMaskShape["CIRCLE"] = "circle"; EKwikIDCameraViewMaskShape["OVAL"] = "oval"; EKwikIDCameraViewMaskShape["RECTANGLE"] = "rectangle"; EKwikIDCameraViewMaskShape["SQUARE"] = "square"; EKwikIDCameraViewMaskShape["NOMASK"] = "nomask"; })(EKwikIDCameraViewMaskShape || (EKwikIDCameraViewMaskShape = {})); const VKwikIDCameraViewMaskShape = [ EKwikIDCameraViewMaskShape.CIRCLE, EKwikIDCameraViewMaskShape.OVAL, EKwikIDCameraViewMaskShape.RECTANGLE, EKwikIDCameraViewMaskShape.SQUARE, EKwikIDCameraViewMaskShape.NOMASK ]; const DEFAULT_CONFIG$2 = { header: { isBack: false, title: "", isClose: true }, footer: { isFlip: false, isSwitch: false }, others: { isFrontCamera: false, flipHorizontal: true, mask: { shape: "NOMASK" }, preview: { show: false, header: { isBack: false, isClose: false } } } }; const BUTTON_PROPERTIES$2 = { appearance_whiteblock: "whiteblock", appearance_secondary: "secondary", appearance_primary: "primary", size_s: "s", size_m: "m" }; // TOOLKIT function flipImageHorizontally$1(base64Image) { // Create an HTML image element const img = new Image(); img.src = base64Image; // Create a canvas element const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Set the canvas dimensions to match the image canvas.width = img.width; canvas.height = img.height; // Draw the image on the canvas, flipped horizontally ctx.scale(-1, 1); // Flip horizontally ctx.drawImage(img, -canvas.width, 0, canvas.width, canvas.height); // Convert the canvas content to a base64 string const flippedBase64 = canvas.toDataURL(); return flippedBase64; } // TOOLKIT function getDevicesList$2() { return __awaiter(this, void 0, void 0, function* () { const videoDevices = []; yield navigator.mediaDevices.enumerateDevices().then((devices) => { for (const device of devices) { if (device.kind === "videoinput") { videoDevices.push(device); } } }); return videoDevices; }); } // TOOLKIT function getDeviceId$1(activeDeviceId) { return __awaiter(this, void 0, void 0, function* () { let deviceId = activeDeviceId; const videoDevices = []; yield navigator.mediaDevices.enumerateDevices().then((devices) => { for (const device of devices) { if (device.kind === "videoinput") { videoDevices.push(device); } } }); if (videoDevices.length === 1 || isEmptyValue(activeDeviceId)) { deviceId = videoDevices[0].deviceId; } else { const filteredVideoDevice = videoDevices.filter((videoDevice) => { return videoDevice.deviceId !== activeDeviceId; })[0]; deviceId = filteredVideoDevice.deviceId; } return deviceId; }); } // TOOLKIT function getImageFromBase64$1(base64String) { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => { resolve(image); }; image.onerror = (error) => { reject(error); }; image.src = base64String; }); } // TOOLKIT function getBase64FromImage$1(image) { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0, image.width, image.height); try { const base64String = canvas.toDataURL("image/png"); resolve(base64String); } catch (error) { reject(error); } }); } // TOOLKIT function getBase64FromImageFile$1(imageFile) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const base64String = reader.result; resolve(base64String); // Resolve the Promise with the base64 string }; reader.onerror = (error) => { reject(error); // Reject the Promise in case of an error }; reader.readAsDataURL(imageFile); }); } // TOOLKIT function getScaledImageFromVideo$1(video, flipped) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const cameraWidth = video.videoWidth; const cameraHeight = video.videoHeight; const visibleWidth = video.clientWidth; const visibleHeight = video.clientHeight; const cameraRatio = cameraWidth / cameraHeight; const visibleRatio = visibleWidth / visibleHeight; let scaledWidth; let scaledHeight; if (cameraRatio >= visibleRatio) { // Scale to Visible Height const heightRatio = visibleHeight / cameraHeight; scaledWidth = cameraWidth * heightRatio; scaledHeight = cameraHeight * heightRatio; } else { // Scale to Visible Width const widthRatio = visibleWidth / cameraWidth; scaledWidth = cameraWidth * widthRatio; scaledHeight = cameraHeight * widthRatio; } canvas.width = scaledWidth; canvas.height = scaledHeight; if (flipped) { context.translate(canvas.width, 0); context.scale(-1, 1); } context.drawImage(video, 0, 0, cameraWidth, cameraHeight, 0, 0, scaledWidth, scaledHeight); // Get the image data from the canvas as a base64-encoded string const imageBase64 = canvas.toDataURL("image/png"); const image = (yield getImageFromBase64$1(imageBase64)); return { image, imageBase64 }; }); } // TOOLKIT function getCroppedImage$1(image, requiredWidth, requiredHeight) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const sx = (image.width - requiredWidth) / 2; const sy = (image.height - requiredHeight) / 2; const sWidth = requiredWidth; const sHeight = requiredHeight; const dx = 0; const dy = 0; const dWidth = requiredWidth; const dHeight = requiredHeight; canvas.width = dWidth; canvas.height = dHeight; context.save(); context.translate(0, 0); context.scale(1, 1); context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); context.restore(); const croppedImageBase64 = canvas.toDataURL("image/png"); const croppedImage = (yield getImageFromBase64$1(croppedImageBase64)); return { image: croppedImage, imageBase64: croppedImageBase64 }; }); } // TOOLKIT function getCroppedImageFromMask$1(image, leftShift, topShift, requiredWidth, requiredHeight) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const sx = leftShift; const sy = topShift; const sWidth = requiredWidth; const sHeight = requiredHeight; const dx = 0; const dy = 0; const dWidth = requiredWidth; const dHeight = requiredHeight; canvas.width = dWidth; canvas.height = dHeight; context.save(); context.translate(0, 0); context.scale(1, 1); context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); context.restore(); const croppedImageBase64 = canvas.toDataURL("image/png"); const croppedImage = (yield getImageFromBase64$1(croppedImageBase64)); return { image: croppedImage, imageBase64: croppedImageBase64 }; }); } function getCorrectImageFormat$1(image) { if (isEmptyValue(image)) { return ""; } if (Array.isArray(image)) { return image.find((img) => typeof img === "string" && img.length !== 0); } if (typeof image === "string") { return image; } return ""; } // TOOLKIT function getWatermarkedImage$1(image, data) { return __awaiter(this, void 0, void 0, function* () { if (isEmptyValue(image)) { throw new Error("No image provided for watermarking!"); } if (isEmptyValue(data)) { return { image, imageBase64: yield getBase64FromImage$1(image), errorMessage: "No data provided for watermarking!" }; } const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); context.font = "12px Arial"; context.textAlign = "left"; context.textBaseline = "top"; context.fillStyle = "#a2a2a2c7"; const longestKey = Object.keys(data).reduce((a, b) => data[a].toString().length > data[b].toString().length ? a : b); const rectangleWidth = context.measureText(data[longestKey]).width; context.fillRect(10, 10, rectangleWidth + 95, 70); context.fillStyle = "yellow"; let yIndex = 20; for (const key in data) { if (isNotEmptyValue(key)) { context.fillText(`${key.split("_").join(" ").toLocaleUpperCase()}: ${data[key]}`, 20, yIndex); yIndex += 20; } } const watermarkedImageBase64 = canvas.toDataURL("image/png"); const watermarkedImage = (yield getImageFromBase64$1(watermarkedImageBase64)); return { image: watermarkedImage, imageBase64: watermarkedImageBase64 }; }); } class KwikIDCameraViewComponent { constructor(kwikuiLoaderService, ref) { this.kwikuiLoaderService = kwikuiLoaderService; this.ref = ref; this.config = DEFAULT_CONFIG$2; this.mediaStream = null; this.currentDeviceId = ""; this.changeDetectionRefInterval = undefined; } ngOnInit() { return __awaiter(this, void 0, void 0, function* () { this.config = mergeObjects({}, DEFAULT_CONFIG$2, this.config); this.changeDetectionRefInterval = setInterval(() => { this.ref.detectChanges(); }, 500); }); } ngOnChanges(changes) { if (checkObjectKeyExists(changes, "config") && !changes.config.firstChange) { this.config = mergeObjects({}, DEFAULT_CONFIG$2, changes.config.currentValue); this.applyCameraSettings(); } } ngAfterViewInit() { return __awaiter(this, void 0, void 0, function* () { this.currentDeviceId = yield getDeviceId$1(null); this.applyCameraSettings(); this.startCamera(); }); } ngOnDestroy() { this.stopCamera(); if (this.changeDetectionRefInterval) { clearInterval(this.changeDetectionRefInterval); } } applyCameraSettings() { var _a; if (this.videoElement) { this.videoElement.nativeElement.style.transform = ((_a = this.config.others) === null || _a === void 0 ? void 0 : _a.flipHorizontal) ? "scaleX(-1); translate(50%, -50%)" : "scaleX(1); translate(-50%, -50%)"; } } startCamera() { navigator.mediaDevices .getUserMedia({ audio: false, video: { facingMode: this.config.others.isFrontCamera ? "user" : "environment" } }) .then((stream) => { this.mediaStream = stream; this.videoElement.nativeElement.srcObject = stream; this.videoElement.nativeElement.play(); }) .catch((error) => { console.log("Error accessing camera:", error); }); } stopCamera() { if (this.mediaStream) { this.mediaStream.getTracks().forEach((track) => track.stop()); this.mediaStream = null; } } } /** @nocollapse */ KwikIDCameraViewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: KwikIDCameraViewComponent, deps: [{ token: i1.KwikUILoaderService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ KwikIDCameraViewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.17", type: KwikIDCameraViewComponent, selector: "kwikid-camera-view", inputs: { config: "config" }, providers: [ { provide: TUI_SANITIZER, useClass: NgDompurifySanitizer } ], viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<video\n #videoElement\n playsinline\n autoplay\n muted\n></video>\n", styles: [":host{position:relative;display:block;width:100%;height:100%}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{border-radius:10px}::-webkit-scrollbar-thumb{background:lightgray;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:gray}.button-icon{width:2.5rem;height:2.5rem;color:#fff;background-color:transparent;border-radius:100%;padding:.5rem;border:none;outline:none;transition:all .2s ease-in-out;filter:drop-shadow(0px 0px 3px black)}.button-icon:hover{transform:scale(1.05)}.button-icon#close tui-svg{font-size:1.4rem}.button-icon tui-svg{width:100%;height:100%}button{cursor:pointer;display:flex;flex-direction:row;align-content:center;justify-content:center;align-items:center}video{width:100%;height:100%;object-fit:cover;overflow:hidden;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}video.flip-horizontal{transform:scaleX(-1) translate(50%,-50%)}#video-switching{position:absolute;width:100%;height:100%;top:0;left:0;object-fit:cover;-o-object-fit:cover;object-position:center;-o-object-position:center;transform:scaleX(1);-webkit-transform:scaleX(1);background:black;color:#fff;display:flex;flex-direction:row;align-content:center;justify-content:center;align-items:center}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "ngOnInit", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "ngOnChanges", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "ngAfterViewInit", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "ngOnDestroy", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "applyCameraSettings", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "startCamera", null); __decorate([ logMethod ], KwikIDCameraViewComponent.prototype, "stopCamera", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: KwikIDCameraViewComponent, decorators: [{ type: Component, args: [{ selector: "kwikid-camera-view", templateUrl: "./kwikid-camera-view.component.html", styleUrls: ["./kwikid-camera-view.component.scss"], providers: [ { provide: TUI_SANITIZER, useClass: NgDompurifySanitizer } ], changeDetection: ChangeDetectionStrategy.OnPush }] }], ctorParameters: function () { return [{ type: i1.KwikUILoaderService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { videoElement: [{ type: ViewChild, args: ["videoElement", { static: true }] }], config: [{ type: Input }], ngOnInit: [], ngOnChanges: [], ngAfterViewInit: [], ngOnDestroy: [], applyCameraSettings: [], startCamera: [], stopCamera: [] } }); const flipSvg = `<svg class="svg-icon" style="width:10em;height:10em;vertical-align:middle;fill:currentColor;overflow:hidden" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M213.333333 259.84v512l-55.04-55.466667a42.666667 42.666667 0 0 0-60.586666 60.586667l128 128a42.666667 42.666667 0 0 0 60.586666 0l128-128a42.666667 42.666667 0 0 0 0-60.586667 42.666667 42.666667 0 0 0-60.586666 0L298.666667 771.84v-512A66.56 66.56 0 0 1 363.946667 192H469.333333a42.666667 42.666667 0 0 0 0-85.333333H363.946667A151.893333 151.893333 0 0 0 213.333333 259.84zM609.706667 247.04a42.666667 42.666667 0 0 0 60.586666 60.586667L725.333333 252.16v512a66.56 66.56 0 0 1-65.28 67.84H554.666667a42.666667 42.666667 0 0 0 0 85.333333h105.386666A151.893333 151.893333 0 0 0 810.666667 764.16v-512l55.04 55.466667a42.666667 42.666667 0 0 0 60.586666 0 42.666667 42.666667 0 0 0 0-60.586667l-128-128a42.666667 42.666667 0 0 0-60.586666 0z"/></svg>`; const uploadSvg = `<svg xmlns="http://www.w3.org/2000/svg" class="svg-icon" style="width: 100%; height: 100%;vertical-align: middle;fill: white;overflow: hidden; padding: 0rem;" viewBox="0 0 1025 1024" version="1.1"><path d="M192.104577 1023.615791a192.104577 192.104577 0 0 1-192.104577-192.104577V447.878374a64.034859 64.034859 0 0 1 128.069718 0v384.017049a64.034859 64.034859 0 0 0 64.034859 64.034859h640.34859a64.034859 64.034859 0 0 0 64.034859-64.034859V448.262583a64.034859 64.034859 0 0 1 128.069718 0v383.63284a192.104577 192.104577 0 0 1-192.104577 192.104577z m256.139436-383.824945V218.569544L365.44694 301.302582a64.034859 64.034859 0 0 1-90.54529-90.417221l192.104577-192.104577a64.034859 64.034859 0 0 1 90.609325 0l177.184455 176.99235a64.034859 64.034859 0 1 1-90.545291 90.353186L576.313731 218.697614v421.093232a64.034859 64.034859 0 1 1-128.069718 0z" fill="white"/></svg>`; const cloudUploadSvg = `<svg xmlns="http://www.w3.org/2000/svg" class="svg-icon" style="width: 10em; height: 10em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1"><path d="M791.5 828.5H651.7c-20.7 0-37.3-16.7-37.3-37.3s16.6-37.3 37.3-37.3h139.9c3.9 0 8-0.1 11.7-0.8 47.2-6.7 81.5-46.4 81.5-92.4 0-30.9-15.4-59.8-41.1-77.1-21.6-14.5-34.1-39.2-33.6-65.9 1.7-111.6-65.2-206.5-166.6-237.1-131.5-39.8-278.4 28.7-320.2 149.8-5.1 15-19.3 25.1-35.2 25.1-41.9 0-82.1 17.8-110.4 48.9-28.6 31.5-42.1 72.4-38 115.1 7.2 75.3 76 134.3 156.6 134.3h209.4c20.6 0 37.3 16.7 37.3 37.3s-16.7 37.3-37.3 37.3H296.2c-118.6 0-219.9-88.6-230.9-201.8-6-63 14.8-125.8 57.1-172.4 36.5-40.2 86.4-65.6 139.8-71.8C326.5 239.3 504.9 160.7 665.1 209 798.7 249.4 887 373.4 884.8 517.5c0 0.8-0.1 2.9-0.2 3.8 47.1 31.5 74.8 83.5 74.8 139.2 0 83-61.9 154.3-143.9 166-6.6 1.4-15.3 2-24 2" fill="currentColor"/><path d="M513.8 719.2c-20.6 0-37.3-16.7-37.3-37.3V431.7c0-20.6 16.7-37.3 37.3-37.3s37.3 16.7 37.3 37.3v250.2c0 20.6-16.7 37.3-37.3 37.3" fill="currentColor"/><path d="M663 580.8c-11 0-21.8-4.8-29.2-14l-120.1-150-120 150c-12.8 16.1-36.3 18.8-52.4 5.8-16.1-12.9-18.7-36.3-5.8-52.4l120.1-150c14.1-17.8 35.4-28 58.2-28 22.8 0 44.1 10.2 58.3 28l120 150c12.9 16.1 10.3 39.6-5.8 52.4-6.9 5.6-15.1 8.2-23.3 8.2" fill="currentColor"/></svg>`; const closeSvg = `<svg xmlns="http://www.w3.org/2000/svg" class="svg-icon" style="width: 10em; height: 10em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1"><path d="M818.1 872.1c-15.4 0-30.7-5.9-42.4-17.6l-613-612.9c-23.4-23.4-23.4-61.4 0-84.9 23.4-23.4 61.4-23.4 84.9 0l612.9 612.9c23.4 23.4 23.4 61.4 0 84.9a59.914 59.914 0 0 1-42.4 17.6z" fill="currentColor"/><path d="M205.1 872.1c-15.4 0-30.7-5.9-42.4-17.6-23.4-23.4-23.4-61.4 0-84.9l612.9-612.9c23.4-23.4 61.4-23.4 84.9 0 23.4 23.4 23.4 61.4 0 84.9L247.6 854.5c-11.7 11.7-27.1 17.6-42.5 17.6z" fill="currentColor"/><path d="M818.1 872.1c-15.4 0-30.7-5.9-42.4-17.6l-613-612.9c-23.4-23.4-23.4-61.4 0-84.9 23.4-23.4 61.4-23.4 84.9 0l612.9 612.9c23.4 23.4 23.4 61.4 0 84.9a59.914 59.914 0 0 1-42.4 17.6z" fill="currentColor"/><path d="M205.1 872.1c-15.4 0-30.7-5.9-42.4-17.6-23.4-23.4-23.4-61.4 0-84.9l612.9-612.9c23.4-23.4 61.4-23.4 84.9 0 23.4 23.4 23.4 61.4 0 84.9L247.6 854.5c-11.7 11.7-27.1 17.6-42.5 17.6z" fill="currentColor"/><path d="M818.1 872.1c-15.4 0-30.7-5.9-42.4-17.6l-613-612.9c-23.4-23.4-23.4-61.4 0-84.9 23.4-23.4 61.4-23.4 84.9 0l612.9 612.9c23.4 23.4 23.4 61.4 0 84.9a59.914 59.914 0 0 1-42.4 17.6z" fill="currentColor"/><path d="M205.1 872.1c-15.4 0-30.7-5.9-42.4-17.6-23.4-23.4-23.4-61.4 0-84.9l612.9-612.9c23.4-23.4 61.4-23.4 84.9 0 23.4 23.4 23.4 61.4 0 84.9L247.6 854.5c-11.7 11.7-27.1 17.6-42.5 17.6z" fill="currentColor"/><path d="M818.1 872.1c-15.4 0-30.7-5.9-42.4-17.6l-613-612.9c-23.4-23.4-23.4-61.4 0-84.9 23.4-23.4 61.4-23.4 84.9 0l612.9 612.9c23.4 23.4 23.4 61.4 0 84.9a59.914 59.914 0 0 1-42.4 17.6z" fill="currentColor"/><path d="M205.1 872.1c-15.4 0-30.7-5.9-42.4-17.6-23.4-23.4-23.4-61.4 0-84.9l612.9-612.9c23.4-23.4 61.4-23.4 84.9 0 23.4 23.4 23.4 61.4 0 84.9L247.6 854.5c-11.7 11.7-27.1 17.6-42.5 17.6z" fill="currentColor"/></svg>`; const DEFAULT_CONFIG$1 = { header: { isBack: true, title: "", isClose: true }, footer: { isUpload: true, isFlip: false, isSwitch: true }, others: { isFrontCamera: false, flipHorizontal: true, isWatermark: false, isWatermarkForImageUpload: false, mask: { shape: undefined }, preview: { show: false, header: { isBack: false, isClose: false } } } }; const BUTTON_PROPERTIES$1 = { appearance_whiteblock: "whiteblock", appearance_secondary: "secondary", appearance_primary: "primary", size_s: "s", size_m: "m" }; // TOOLKIT function flipImageHorizontally(base64Image) { // Create an HTML image element const img = new Image(); img.src = base64Image; // Create a canvas element const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Set the canvas dimensions to match the image canvas.width = img.width; canvas.height = img.height; // Draw the image on the canvas, flipped horizontally ctx.scale(-1, 1); // Flip horizontally ctx.drawImage(img, -canvas.width, 0, canvas.width, canvas.height); // Convert the canvas content to a base64 string const flippedBase64 = canvas.toDataURL(); return flippedBase64; } // TOOLKIT function getDevicesList$1() { return __awaiter(this, void 0, void 0, function* () { const videoDevices = []; yield navigator.mediaDevices.enumerateDevices().then((devices) => { for (const device of devices) { if (device.kind === "videoinput") { videoDevices.push(device); } } }); return videoDevices; }); } // TOOLKIT function getDeviceId(activeDeviceId) { return __awaiter(this, void 0, void 0, function* () { let deviceId = activeDeviceId; const videoDevices = []; yield navigator.mediaDevices.enumerateDevices().then((devices) => { for (const device of devices) { if (device.kind === "videoinput") { videoDevices.push(device); } } }); if (videoDevices.length === 1 || isEmptyValue(activeDeviceId)) { deviceId = videoDevices[0].deviceId; } else { const filteredVideoDevice = videoDevices.filter((videoDevice) => { return videoDevice.deviceId !== activeDeviceId; })[0]; deviceId = filteredVideoDevice.deviceId; } return deviceId; }); } // TOOLKIT function getImageFromBase64(base64String) { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => { resolve(image); }; image.onerror = (error) => { reject(error); }; image.src = base64String; }); } // TOOLKIT function getBase64FromImage(image) { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0, image.width, image.height); try { const base64String = canvas.toDataURL("image/png"); resolve(base64String); } catch (error) { reject(error); } }); } // TOOLKIT function getBase64FromImageFile(imageFile) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const base64String = reader.result; resolve(base64String); // Resolve the Promise with the base64 string }; reader.onerror = (error) => { reject(error); // Reject the Promise in case of an error }; reader.readAsDataURL(imageFile); }); } // TOOLKIT function getScaledImageFromVideo(video, flipped) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const cameraWidth = video.videoWidth; const cameraHeight = video.videoHeight; const visibleWidth = video.clientWidth; const visibleHeight = video.clientHeight; const cameraRatio = cameraWidth / cameraHeight; const visibleRatio = visibleWidth / visibleHeight; let scaledWidth; let scaledHeight; if (cameraRatio >= visibleRatio) { // Scale to Visible Height const heightRatio = visibleHeight / cameraHeight; scaledWidth = cameraWidth * heightRatio; scaledHeight = cameraHeight * heightRatio; } else { // Scale to Visible Width const widthRatio = visibleWidth / cameraWidth; scaledWidth = cameraWidth * widthRatio; scaledHeight = cameraHeight * widthRatio; } canvas.width = scaledWidth; canvas.height = scaledHeight; if (flipped) { context.translate(canvas.width, 0); context.scale(-1, 1); } context.drawImage(video, 0, 0, cameraWidth, cameraHeight, 0, 0, scaledWidth, scaledHeight); // Get the image data from the canvas as a base64-encoded string const imageBase64 = canvas.toDataURL("image/png"); const image = (yield getImageFromBase64(imageBase64)); return { image, imageBase64 }; }); } // TOOLKIT function getCroppedImage(image, requiredWidth, requiredHeight) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const sx = (image.width - requiredWidth) / 2; const sy = (image.height - requiredHeight) / 2; const sWidth = requiredWidth; const sHeight = requiredHeight; const dx = 0; const dy = 0; const dWidth = requiredWidth; const dHeight = requiredHeight; canvas.width = dWidth; canvas.height = dHeight; context.save(); context.translate(0, 0); context.scale(1, 1); context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); context.restore(); const croppedImageBase64 = canvas.toDataURL("image/png"); const croppedImage = (yield getImageFromBase64(croppedImageBase64)); return { image: croppedImage, imageBase64: croppedImageBase64 }; }); } // TOOLKIT function getCroppedImageFromMask(image, leftShift, topShift, requiredWidth, requiredHeight) { return __awaiter(this, void 0, void 0, function* () { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const sx = leftShift; const sy = topShift; const sWidth = requiredWidth; const sHeight = requiredHeight; const dx = 0; const dy = 0; const dWidth = requiredWidth; const dHeight = requiredHeight; canvas.width = dWidth; canvas.height = dHeight; context.save(); context.translate(0, 0); context.scale(1, 1); context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); context.restore(); const croppedImageBase64 = canvas.toDataURL("image/png"); const croppedImage = (yield getImageFromBase64(croppedImageBase64)); return { image: croppedImage, imageBase64: croppedImageBase64 }; }); } function getCorrectImageFormat(image) { if (isEmptyValue(image)) { return ""; } if (Array.isArray(image)) { return image.find((img) => typeof img === "string" && img.length !== 0); } if (typeof image === "string") { return image; } return ""; } // TOOLKIT function getWatermarkedImage(image, data) { return __awaiter(this, void 0, void 0, function* () { if (isEmptyValue(image)) { throw new Error("No image provided for watermarking!"); } if (isEmptyValue(data)) { return { image, imageBase64: yield getBase64FromImage(image), errorMessage: "No data provided for watermarking!" }; } const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); context.font = "12px Arial"; context.textAlign = "left"; context.textBaseline = "top"; context.fillStyle = "#a2a2a2c7"; const longestKey = Object.keys(data).reduce((a, b) => data[a].toString().length > data[b].toString().length ? a : b); const rectangleWidth = context.measureText(data[longestKey]).width; context.fillRect(10, 10, rectangleWidth + 95, 70); context.fillStyle = "yellow"; let yIndex = 20; for (const key in data) { if (isNotEmptyValue(key)) { context.fillText(`${key.split("_").join(" ").toLocaleUpperCase()}: ${data[key]}`, 20, yIndex); yIndex += 20; } } const watermarkedImageBase64 = canvas.toDataURL("image/png"); const watermarkedImage = (yield getImageFromBase64(watermarkedImageBase64)); return { image: watermarkedImage, imageBase64: watermarkedImageBase64 }; }); } class KwikIDCameraCaptureComponent { constructor(kwikuiLoaderService, alert, ref) { this.kwikuiLoaderService = kwikuiLoaderService; this.alert = alert; this.ref = ref; this.SVG = { flipSvg, uploadSvg, cloudUploadSvg, closeSvg }; this.BUTTON_PROPS = BUTTON_PROPERTIES$1; this.config = DEFAULT_CONFIG$1; this.image = ""; this.originalImage = ""; this.croppedImage = ""; this.maskedImage = ""; this.data = { watermark: {} }; this.getImage = new EventEmitter(); this.onClickBack = new EventEmitter(); this.onClickClose = new EventEmitter(); this.onClickSave = new EventEmitter(); this.imagePreviewVisible = false; this.files = []; this.mediaStream = null; this.devicesList = []; this.currentDeviceId = ""; this.changeDetectionRefInterval = undefined; // Camera Settings this.isSwitching = false; // Misc Methods this.loading = undefined; } showImagePreview() { var _a, _b, _c; if ((_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.others) === null || _b === void 0 ? void 0 : _b.preview) === null || _c === void 0 ? void 0 : _c.show) { this.image = getCorrectImageFormat(this.image); this.originalImage = this.image; this.previewElement.nativeElement.style.display = "flex"; this.imagePreviewVisible = true; } else { this.hideImagePreview(); } } hideImagePreview() { this.previewElement.nativeElement.style.display = "none"; this.imagePreviewVisible = false; } ngOnInit() { return __awaiter(this, void 0, void 0, function* () { this.config = mergeObjects({}, DEFAULT_CONFIG$1, this.config); if (isNotEmptyValue(this.image)) { this.showImagePreview(); } this.devicesList = yield getDevicesList$1(); this.changeDetectionRefInterval = setInterval(() => { this.ref.detectChanges(); }, 500); }); } ngOnChanges(changes) { const verifyChange = (key) => { return checkObjectKeyExists(changes, key) && !changes[key].firstChange; }; if (verifyChange("config")) { this.config = mergeObjects({}, DEFAULT_CONFIG$1, changes.config.currentValue); } if (verifyChange("image")) { this.image = changes.image.currentValue; if (isNotEmptyValue(this.image)) { this.showImagePreview(); } } if (verifyChange("data")) { this.data = getObjectDeepCopy(changes.data.currentValue); } } ngAfterViewInit() { return __awaiter(this, void 0, void 0, function* () { if (isNotEmptyValue(this.image)) { this.showImagePreview(); } this.currentDeviceId = yield getDeviceId(null); this.correctMirror(); this.startCamera(); }); } ngOnDestroy() { this.stopCamera(); this.image = ""; this.originalImage = ""; this.croppedImage = ""; this.maskedImage = ""; if (this.changeDetectionRefInterval) { clearInterval(this.changeDetectionRefInterval); } } startCamera() { this.hideImagePreview(); this.handleMaskingRatio(); navigator.mediaDevices .getUserMedia({ audio: false, video: { facingMode: this.config.others.isFrontCamera ? "user" : "environment" } }) .then((stream) => { this.dismissLoading(); this.mediaStream = stream; this.videoElement.nativeElement.srcObject = stream; this.videoElement.nativeElement.play(); }) .catch((error) => { console.log("Error accessing camera:", error); }); } stopCamera() { if (this.mediaStream) { this.mediaStream.getTracks().forEach((track) => { track.stop(); }); this.mediaStream = null; this.videoElement.nativeElement.srcObject = null; } } capture() { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { // this.presentLoading(); const video = this.videoElement.nativeElement; const mask = this.maskElement.nativeElement; const requiredWidth = video.clientWidth; const requiredHeight = video.clientHeight; const { image: scaledImage, imageBase64: scaledImageBase64 } = yield getScaledImageFromVideo(video, this.config.others.flipHorizontal); const { image: croppedImage, imageBase64: croppedImageBase64 } = yield getCroppedImage(scaledImage, requiredWidth, requiredHeight); const { image: maskedImage, imageBase64: maskedImageBase64 } = yield getCroppedImageFromMask(croppedImage, mask.getBoundingClientRect().x - video.getBoundingClientRect().x, mask.getBoundingClientRect().y - video.getBoundingClientRect().y, mask.getBoundingClientRect().width, mask.getBoundingClientRect().height); if ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.others) === null || _b === void 0 ? void 0 : _b.isWatermark) { const { image: scaledImageWithWatermark, imageBase64: scaledImageWithWatermarkBase64 } = yield getWatermarkedImage(scaledImage, (_c = this.data) === null || _c === void 0 ? void 0 : _c.watermark); const { image: croppedImageWithWatermark, imageBase64: croppedImageWithWatermarkBase64 } = yield getWatermarkedImage(croppedImage, (_d = this.data) === null || _d === void 0 ? void 0 : _d.watermark); const { image: maskedImageWithWatermark, imageBase64: maskedImageWithWatermarkBase64 } = yield getWatermarkedImage(maskedImage, (_e = this.data) === null || _e === void 0 ? void 0 : _e.watermark); /** * Commented since before showing preview, * we might want to process the image at backend which will take time. * So till then we can show loader instead of showing the original captured image. */ // this.image = scaledImageBase64; this.originalImage = scaledImageWithWatermarkBase64; this.croppedImage = croppedImageWithWatermarkBase64; this.maskedImage = maskedImageWithWatermarkBase64; } else { /** * Commented since before showing preview, * we might want to process the image at backend which will take time. * So till then we can show loader instead of showing the original captured image. */ // this.image = scaledImageWithWatermarkBase64; this.originalImage = scaledImageBase64; this.croppedImage = croppedImageBase64; this.maskedImage = maskedImageBase64; } this.getImage.emit({ originalImage: this.originalImage, croppedImage: this.croppedImage, maskedImage: this.maskedImage }); }); } back() { this.onClickBack.emit({}); } close() { this.onClickClose.emit({}); } retry() { this.image = ""; this.originalImage = ""; this.croppedImage = ""; this.maskedImage = ""; this.hideImagePreview(); this.startCamera(); } save() { this.onClickSave.emit({ originalImage: this.originalImage, croppedImage: this.croppedImage, maskedImage: this.maskedImage }); } handleOnSelectInputFile() { const file = this.inputFile.nativeElement.files[0]; const reader = new FileReader(); reader.onloadend = () => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; /** * Commented since before showing preview, * we might want to process the image at backend which will take time. * So till then we can show loader instead of showing the original captured image. */ let uploadedImageStr = reader.result; let uploadedImage = yield getImageFromBase64(uploadedImageStr); if ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.others) === null || _b === void 0 ? void 0 : _b.isWatermarkForImageUpload) { const { image: imageWithWatermark, imageBase64: imageWithWatermarkBase64 } = yield getWatermarkedImage(uploadedImage, (_c = this.data) === null || _c === void 0 ? void 0 : _c.watermark); uploadedImageStr = imageWithWatermarkBase64; uploadedImage = imageWithWatermark; } this.image = uploadedImageStr; this.originalImage = uploadedImageStr; this.croppedImage = ""; this.maskedImage = ""; this.getImage.emit({ originalImage: this.originalImage }); }); reader.readAsDataURL(file); } handleMaskingRatio() { var _a, _b, _c; const maskElement = this.maskElement.nativeElement; const videoElement = this.videoElement.nativeElement; const rect = videoElement.getBoundingClientRect(); const innerWidth = rect.width; const innerHeight = rect.height; const shape = (_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.others) === null || _b === void 0 ? void 0 : _b.mask) === null || _c === void 0 ? void 0 : _c.shape; if (isNotEmptyString(shape)) { switch (shape) { case "OVAL": if (innerWidth > innerHeight) { maskElement.style.setProperty("width", "calc(var(--height) * 5 / 8)"); } else { maskElement.style.setProperty("height", "calc(var(--width) * 8 / 5)"); } break; default: break; } } } upload() { if (this.inputFile) { this.inputFile.nativeElement.click(); } } flip() { this.config.others.flipHorizontal = !this.config.others.flipHorizontal; } // Helpers Methods correctMirror() { if (this.config.others.isFrontCamera) { this.config.others.flipHorizontal = true; } else { this.config.others.flipHorizontal = false; } } switchCamera() { return __awaiter(this, void 0, void 0, function* () { // Commented the below code because we can use video.facingMode directly. // if (this.devicesList.length > 1) { // this.isSwitching = true; // this.presentLoading("Switching..."); // // this.config.others.isFrontCamera = !this.config.others.isFrontCamera; // this.correctMirror(); // // this.currentDeviceId = await getDeviceId(this.currentDeviceId); // // this.startCamera(); // // setTimeout(() => { // this.isSwitching = false; // this.dismissLoading(); // }, 500); // } this.isSwitching = true; this.config.others.isFrontCamera = !this.config.others.isFrontCamera; this.correctMirror(); this.startCamera(); setTimeout(() => { this.isSwitching = false; }, 500); }); } presentLoading(msg) { this.loading = true; this.kwikuiLoaderService.show({ loaderText: isNotEmptyValue(msg) ? msg : "Please wait...", fullscreen: true }); } dismissLoading() { this.loading = false; this.kwikuiLoaderService.hide(); } } /** @nocollapse */ KwikIDCameraCaptureComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: KwikIDCameraCaptureComponent, deps: [{ token: i1.KwikUILoaderService }, { token: TuiAlertService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ KwikIDCameraCaptureComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.17", type: KwikIDCameraCaptureComponent, selector: "kwikid-camera-capture", inputs: { config: "config", image: "image", data: "data" }, outputs: { getImage: "getImage", onClickBack: "onClickBack", onClickClose: "onClickClose", onClickSave: "onClickSave" }, providers: [ { provide: TUI_SANITIZER, useClass: NgDompurifySanitizer } ], viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true, static: true }, { propertyName: "previewElement", first: true, predicate: ["previewElement"], descendants: true, static: true }, { propertyName: "maskElement", first: true, predicate: ["maskElement"], descendants: true }, { propertyName: "inputFile", first: true, predicate: ["inputFile"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<video\n #videoElement\n [class.flip-horizontal]=\"config.others?.flipHorizontal\"\n></video>\n\n<div\n id=\"video-switching\"\n *ngIf=\"isSwitching\"\n>\n Switching...\n</div>\n\n<input\n type=\"file\"\n accept=\"image/*,capture=camera\"\n multiple=\"false\"\n #inputFile\n (change)=\"handleOnSelectInputFile()\"\n (click)=\"inputFile.value = null\"\n style=\"visibility: hidden; position: absolute; top: -10000px; left: -10000px\"\n/>\n\n<div class=\"camera-capture\">\n <div class=\"video-frame\">\n <header>\n <ng-content select=\"[slot='capture-header']\"></ng-content>\n </header>\n <main>\n <div\n #maskElement\n [class.mask]=\"config?.others?.mask !== undefined\"\n [class.shape-square]=\"config?.others?.mask?.shape === 'SQUARE'\"\n [class.shape-rectangle]=\"config?.others?.mask?.shape === 'RECTANGLE'\"\n [class.shape-oval]=\"config?.others?.mask?.shape === 'OVAL'\"\n [class.shape-circle]=\"config?.others?.mask?.shape === 'CIRCLE'\"\n ></div>\n </main>\n <footer>\n <div class=\"footer-left\">\n <kwikui-button\n *ngIf=\"config?.footer?.isUpload\"\n [appearance]=\"BUTTON_PROPS.appearance_whiteblock\"\n icon=\"tuiIconUpload\"\n label=\"UPLOAD\"\n shape=\"rounded\"\n [size]=\"BUTTON_PROPS.size_s\"\n (onClick)=\"upload()\"\n ></kwikui-button>\n </div>\n <div class=\"footer-center\">\n <button\n id=\"capture\"\n (click)=\"capture()\"\n >\n </button>\n </div>\n <div class=\"footer-right\">\n <kwikui-button\n *ngIf=\"config?.footer?.isFlip && false\"\n [appearance]=\"BUTTON_PROPS.appearance_whiteblock\"\n icon=\"tuiIconRefreshCcw\"\n label=\"FLIP\"\n shape=\"rounded\"\n [size]=\"BUTTON_PROPS.size_s\"\n (onClick)=\"flip()\"\n ></kwikui-button>\n <kwikui-button\n *ngIf=\"config?.footer?.isSwitch && devicesList.length > 1\"\n [appearance]=\"BUTTON_PROP