kwikid-camera
Version:
KwikID's Camera Component
1,062 lines (1,051 loc) • 143 kB
JavaScript
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