UNPKG

ngx-document-scanner

Version:

Angular 2+ component for cropping and enhancing images of documents

851 lines 104 kB
import { __awaiter, __decorate, __generator, __metadata, __read, __spread } from "tslib"; import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { LimitsService, PointPositionChange, PositionChangeData, RolesArray } from '../../services/limits.service'; import { MatBottomSheet } from '@angular/material/bottom-sheet'; import { NgxFilterMenuComponent } from '../filter-menu/ngx-filter-menu.component'; import { NgxOpenCVService } from 'ngx-opencv'; var NgxDocScannerComponent = /** @class */ (function () { function NgxDocScannerComponent(ngxOpenCv, limitsService, bottomSheet) { var _this = this; this.ngxOpenCv = ngxOpenCv; this.limitsService = limitsService; this.bottomSheet = bottomSheet; // ************* // // EDITOR CONFIG // // ************* // /** * an array of action buttons displayed on the editor screen */ this.editorButtons = [ { name: 'exit', action: function () { _this.exitEditor.emit('canceled'); }, icon: 'arrow_back', type: 'fab', mode: 'crop' }, { name: 'rotate', action: this.rotateImage.bind(this), icon: 'rotate_right', type: 'fab', mode: 'crop' }, { name: 'done_crop', action: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: this.mode = 'color'; return [4 /*yield*/, this.transform()]; case 1: _a.sent(); return [4 /*yield*/, this.applyFilter(true)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }, icon: 'done', type: 'fab', mode: 'crop' }, { name: 'back', action: function () { _this.mode = 'crop'; _this.loadFile(_this.originalImage); }, icon: 'arrow_back', type: 'fab', mode: 'color' }, { name: 'filter', action: function () { return _this.chooseFilters(); }, icon: 'photo_filter', type: 'fab', mode: 'color' }, { name: 'upload', action: this.exportImage.bind(this), icon: 'cloud_upload', type: 'fab', mode: 'color' }, ]; /** * true after the image is loaded and preview is displayed */ this.imageLoaded = false; /** * editor mode */ this.mode = 'crop'; /** * filter selected by the user, returned by the filter selector bottom sheet */ this.selectedFilter = 'default'; /** * image dimensions */ this.imageDimensions = { width: 0, height: 0 }; // ************** // // EVENT EMITTERS // // ************** // /** * optional binding to the exit button of the editor */ this.exitEditor = new EventEmitter(); /** * fires on edit completion */ this.editResult = new EventEmitter(); /** * emits errors, can be linked to an error handler of choice */ this.error = new EventEmitter(); /** * emits the loading status of the cv module. */ this.ready = new EventEmitter(); /** * emits true when processing is done, false when completed */ this.processing = new EventEmitter(); this.screenDimensions = { width: window.innerWidth, height: window.innerHeight }; // subscribe to status of cv module this.ngxOpenCv.cvState.subscribe(function (cvState) { _this.cvState = cvState.state; _this.ready.emit(cvState.ready); if (cvState.error) { _this.error.emit(new Error('error loading cv')); } else if (cvState.loading) { _this.processing.emit(true); } else if (cvState.ready) { _this.processing.emit(false); } }); // subscribe to positions of crop tool this.limitsService.positions.subscribe(function (points) { _this.points = points; }); } Object.defineProperty(NgxDocScannerComponent.prototype, "displayedButtons", { /** * returns an array of buttons according to the editor mode */ get: function () { var _this = this; return this.editorButtons.filter(function (button) { return button.mode === _this.mode; }); }, enumerable: true, configurable: true }); Object.defineProperty(NgxDocScannerComponent.prototype, "file", { // ****** // // INPUTS // // ****** // /** * set image for editing * @param file - file from form input */ set: function (file) { var _this = this; if (file) { setTimeout(function () { _this.processing.emit(true); }, 5); this.imageLoaded = false; this.originalImage = file; this.ngxOpenCv.cvState.subscribe(function (cvState) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!cvState.ready) return [3 /*break*/, 2]; // read file to image & canvas return [4 /*yield*/, this.loadFile(file)]; case 1: // read file to image & canvas _a.sent(); this.processing.emit(false); _a.label = 2; case 2: return [2 /*return*/]; } }); }); }); } }, enumerable: true, configurable: true }); NgxDocScannerComponent.prototype.ngOnInit = function () { var _this = this; // set options from config object this.options = new ImageEditorConfig(this.config); // set export image icon this.editorButtons.forEach(function (button) { if (button.name === 'upload') { button.icon = _this.options.exportImageIcon; } }); this.maxPreviewWidth = this.options.maxPreviewWidth; this.editorStyle = this.options.editorStyle; }; // ***************************** // // editor action buttons methods // // ***************************** // /** * emits the exitEditor event */ NgxDocScannerComponent.prototype.exit = function () { this.exitEditor.emit('canceled'); }; /** * applies the selected filter, and when done emits the resulted image */ NgxDocScannerComponent.prototype.exportImage = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.applyFilter(false)]; case 1: _a.sent(); if (this.options.maxImageDimensions) { this.resize(this.editedImage) .then(function (resizeResult) { resizeResult.toBlob(function (blob) { _this.editResult.emit(blob); _this.processing.emit(false); }, _this.originalImage.type); }); } else { this.editedImage.toBlob(function (blob) { _this.editResult.emit(blob); _this.processing.emit(false); }, this.originalImage.type); } return [2 /*return*/]; } }); }); }; /** * open the bottom sheet for selecting filters, and applies the selected filter in preview mode */ NgxDocScannerComponent.prototype.chooseFilters = function () { var _this = this; var data = { filter: this.selectedFilter }; var bottomSheetRef = this.bottomSheet.open(NgxFilterMenuComponent, { data: data }); bottomSheetRef.afterDismissed().subscribe(function () { _this.selectedFilter = data.filter; _this.applyFilter(true); }); }; // *************************** // // File Input & Output Methods // // *************************** // /** * load image from input field */ NgxDocScannerComponent.prototype.loadFile = function (file) { var _this = this; return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var err_1, err_2; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: this.processing.emit(true); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.readImage(file)]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); console.error(err_1); this.error.emit(new Error(err_1)); return [3 /*break*/, 4]; case 4: _a.trys.push([4, 6, , 7]); return [4 /*yield*/, this.showPreview()]; case 5: _a.sent(); return [3 /*break*/, 7]; case 6: err_2 = _a.sent(); console.error(err_2); this.error.emit(new Error(err_2)); return [3 /*break*/, 7]; case 7: // set pane limits // show points this.imageLoaded = true; return [4 /*yield*/, this.limitsService.setPaneDimensions({ width: this.previewDimensions.width, height: this.previewDimensions.height })]; case 8: _a.sent(); setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.detectContours()]; case 1: _a.sent(); this.processing.emit(false); resolve(); return [2 /*return*/]; } }); }); }, 15); return [2 /*return*/]; } }); }); }); }; /** * read image from File object */ NgxDocScannerComponent.prototype.readImage = function (file) { var _this = this; return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var imageSrc, err_3, img; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, readFile()]; case 1: imageSrc = _a.sent(); return [3 /*break*/, 3]; case 2: err_3 = _a.sent(); reject(err_3); return [3 /*break*/, 3]; case 3: img = new Image(); img.onload = function () { return __awaiter(_this, void 0, void 0, function () { var ctx, width, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: // set edited image canvas and dimensions this.editedImage = document.createElement('canvas'); this.editedImage.width = img.width; this.editedImage.height = img.height; ctx = this.editedImage.getContext('2d'); ctx.drawImage(img, 0, 0); width = img.width > img.height ? img.height : img.width; if (!(width > this.options.maxImageDimensions.width)) return [3 /*break*/, 2]; _a = this; return [4 /*yield*/, this.resize(this.editedImage)]; case 1: _a.editedImage = _b.sent(); _b.label = 2; case 2: this.imageDimensions.width = this.editedImage.width; this.imageDimensions.height = this.editedImage.height; this.setPreviewPaneDimensions(this.editedImage); resolve(); return [2 /*return*/]; } }); }); }; img.src = imageSrc; return [2 /*return*/]; } }); }); }); /** * read file from input field */ function readFile() { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function (event) { resolve(reader.result); }; reader.onerror = function (err) { reject(err); }; reader.readAsDataURL(file); }); } }; // ************************ // // Image Processing Methods // // ************************ // /** * rotate image 90 degrees */ NgxDocScannerComponent.prototype.rotateImage = function () { var _this = this; return new Promise(function (resolve, reject) { _this.processing.emit(true); setTimeout(function () { var dst = cv.imread(_this.editedImage); // const dst = new cv.Mat(); cv.transpose(dst, dst); cv.flip(dst, dst, 1); cv.imshow(_this.editedImage, dst); // src.delete(); dst.delete(); // save current preview dimensions and positions var initialPreviewDimensions = { width: 0, height: 0 }; Object.assign(initialPreviewDimensions, _this.previewDimensions); var initialPositions = Array.from(_this.points); // get new dimensions // set new preview pane dimensions _this.setPreviewPaneDimensions(_this.editedImage); // get preview pane resize ratio var previewResizeRatios = { width: _this.previewDimensions.width / initialPreviewDimensions.width, height: _this.previewDimensions.height / initialPreviewDimensions.height }; // set new preview pane dimensions _this.limitsService.rotateClockwise(previewResizeRatios, initialPreviewDimensions, initialPositions); _this.showPreview().then(function () { _this.processing.emit(false); resolve(); }); }, 30); }); }; /** * detects the contours of the document and **/ NgxDocScannerComponent.prototype.detectContours = function () { var _this = this; return new Promise(function (resolve, reject) { _this.processing.emit(true); setTimeout(function () { // load the image and compute the ratio of the old height to the new height, clone it, and resize it var processingResizeRatio = 0.5; var dst = cv.imread(_this.editedImage); var dsize = new cv.Size(dst.rows * processingResizeRatio, dst.cols * processingResizeRatio); var ksize = new cv.Size(5, 5); // convert the image to grayscale, blur it, and find edges in the image cv.cvtColor(dst, dst, cv.COLOR_RGBA2GRAY, 0); cv.GaussianBlur(dst, dst, ksize, 0, 0, cv.BORDER_DEFAULT); cv.Canny(dst, dst, 75, 200); // find contours cv.threshold(dst, dst, 120, 200, cv.THRESH_BINARY); var contours = new cv.MatVector(); var hierarchy = new cv.Mat(); cv.findContours(dst, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE); var rect = cv.boundingRect(dst); dst.delete(); hierarchy.delete(); contours.delete(); // transform the rectangle into a set of points Object.keys(rect).forEach(function (key) { rect[key] = rect[key] * _this.imageResizeRatio; }); var contourCoordinates = [ new PositionChangeData({ x: rect.x, y: rect.y }, ['left', 'top']), new PositionChangeData({ x: rect.x + rect.width, y: rect.y }, ['right', 'top']), new PositionChangeData({ x: rect.x + rect.width, y: rect.y + rect.height }, ['right', 'bottom']), new PositionChangeData({ x: rect.x, y: rect.y + rect.height }, ['left', 'bottom']), ]; _this.limitsService.repositionPoints(contourCoordinates); // this.processing.emit(false); resolve(); }, 30); }); }; /** * apply perspective transform */ NgxDocScannerComponent.prototype.transform = function () { var _this = this; return new Promise(function (resolve, reject) { _this.processing.emit(true); setTimeout(function () { var dst = cv.imread(_this.editedImage); // create source coordinates matrix var sourceCoordinates = [ _this.getPoint(['top', 'left']), _this.getPoint(['top', 'right']), _this.getPoint(['bottom', 'right']), _this.getPoint(['bottom', 'left']) ].map(function (point) { return [point.x / _this.imageResizeRatio, point.y / _this.imageResizeRatio]; }); // get max width var bottomWidth = _this.getPoint(['bottom', 'right']).x - _this.getPoint(['bottom', 'left']).x; var topWidth = _this.getPoint(['top', 'right']).x - _this.getPoint(['top', 'left']).x; var maxWidth = Math.max(bottomWidth, topWidth) / _this.imageResizeRatio; // get max height var leftHeight = _this.getPoint(['bottom', 'left']).y - _this.getPoint(['top', 'left']).y; var rightHeight = _this.getPoint(['bottom', 'right']).y - _this.getPoint(['top', 'right']).y; var maxHeight = Math.max(leftHeight, rightHeight) / _this.imageResizeRatio; // create dest coordinates matrix var destCoordinates = [ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1] ]; // convert to open cv matrix objects var Ms = cv.matFromArray(4, 1, cv.CV_32FC2, [].concat.apply([], __spread(sourceCoordinates))); var Md = cv.matFromArray(4, 1, cv.CV_32FC2, [].concat.apply([], __spread(destCoordinates))); var transformMatrix = cv.getPerspectiveTransform(Ms, Md); // set new image size var dsize = new cv.Size(maxWidth, maxHeight); // perform warp cv.warpPerspective(dst, dst, transformMatrix, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar()); cv.imshow(_this.editedImage, dst); dst.delete(); Ms.delete(); Md.delete(); transformMatrix.delete(); _this.setPreviewPaneDimensions(_this.editedImage); _this.showPreview().then(function () { _this.processing.emit(false); resolve(); }); }, 30); }); }; /** * applies the selected filter to the image * @param preview - when true, will not apply the filter to the edited image but only display a preview. * when false, will apply to editedImage */ NgxDocScannerComponent.prototype.applyFilter = function (preview) { var _this = this; return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var options, dst; var _this = this; return __generator(this, function (_a) { this.processing.emit(true); options = { blur: false, th: true, thMode: cv.ADAPTIVE_THRESH_MEAN_C, thMeanCorrection: 10, thBlockSize: 25, thMax: 255, grayScale: true, }; dst = cv.imread(this.editedImage); switch (this.selectedFilter) { case 'original': options.th = false; options.grayScale = false; options.blur = false; break; case 'magic_color': options.grayScale = false; break; case 'bw2': options.thMode = cv.ADAPTIVE_THRESH_GAUSSIAN_C; options.thMeanCorrection = 15; options.thBlockSize = 15; break; case 'bw3': options.blur = true; options.thMeanCorrection = 15; break; } setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { var ksize; return __generator(this, function (_a) { switch (_a.label) { case 0: if (options.grayScale) { cv.cvtColor(dst, dst, cv.COLOR_RGBA2GRAY, 0); } if (options.blur) { ksize = new cv.Size(5, 5); cv.GaussianBlur(dst, dst, ksize, 0, 0, cv.BORDER_DEFAULT); } if (options.th) { if (options.grayScale) { cv.adaptiveThreshold(dst, dst, options.thMax, options.thMode, cv.THRESH_BINARY, options.thBlockSize, options.thMeanCorrection); } else { dst.convertTo(dst, -1, 1, 60); cv.threshold(dst, dst, 170, 255, cv.THRESH_BINARY); } } if (!preview) { cv.imshow(this.editedImage, dst); } return [4 /*yield*/, this.showPreview(dst)]; case 1: _a.sent(); this.processing.emit(false); resolve(); return [2 /*return*/]; } }); }); }, 30); return [2 /*return*/]; }); }); }); }; /** * resize an image to fit constraints set in options.maxImageDimensions */ NgxDocScannerComponent.prototype.resize = function (image) { var _this = this; return new Promise(function (resolve, reject) { _this.processing.emit(true); setTimeout(function () { var src = cv.imread(image); var currentDimensions = { width: src.size().width, height: src.size().height }; var resizeDimensions = { width: 0, height: 0 }; if (currentDimensions.width > _this.options.maxImageDimensions.width) { resizeDimensions.width = _this.options.maxImageDimensions.width; resizeDimensions.height = _this.options.maxImageDimensions.width / currentDimensions.width * currentDimensions.height; if (resizeDimensions.height > _this.options.maxImageDimensions.height) { resizeDimensions.height = _this.options.maxImageDimensions.height; resizeDimensions.width = _this.options.maxImageDimensions.height / currentDimensions.height * currentDimensions.width; } var dsize = new cv.Size(Math.floor(resizeDimensions.width), Math.floor(resizeDimensions.height)); cv.resize(src, src, dsize, 0, 0, cv.INTER_AREA); var resizeResult = document.createElement('canvas'); cv.imshow(resizeResult, src); src.delete(); _this.processing.emit(false); resolve(resizeResult); } else { _this.processing.emit(false); resolve(image); } }, 30); }); }; /** * display a preview of the image on the preview canvas */ NgxDocScannerComponent.prototype.showPreview = function (image) { var _this = this; return new Promise(function (resolve, reject) { var src; if (image) { src = image; } else { src = cv.imread(_this.editedImage); } var dst = new cv.Mat(); var dsize = new cv.Size(0, 0); cv.resize(src, dst, dsize, _this.imageResizeRatio, _this.imageResizeRatio, cv.INTER_AREA); cv.imshow(_this.previewCanvas.nativeElement, dst); src.delete(); dst.delete(); resolve(); }); }; // *************** // // Utility Methods // // *************** // /** * set preview canvas dimensions according to the canvas element of the original image */ NgxDocScannerComponent.prototype.setPreviewPaneDimensions = function (img) { // set preview pane dimensions this.previewDimensions = this.calculateDimensions(img.width, img.height); this.previewCanvas.nativeElement.width = this.previewDimensions.width; this.previewCanvas.nativeElement.height = this.previewDimensions.height; this.imageResizeRatio = this.previewDimensions.width / img.width; this.imageDivStyle = { width: this.previewDimensions.width + this.options.cropToolDimensions.width + 'px', height: this.previewDimensions.height + this.options.cropToolDimensions.height + 'px', 'margin-left': "calc((100% - " + (this.previewDimensions.width + 10) + "px) / 2 + " + this.options.cropToolDimensions.width / 2 + "px)", 'margin-right': "calc((100% - " + (this.previewDimensions.width + 10) + "px) / 2 - " + this.options.cropToolDimensions.width / 2 + "px)", }; this.limitsService.setPaneDimensions({ width: this.previewDimensions.width, height: this.previewDimensions.height }); }; /** * calculate dimensions of the preview canvas */ NgxDocScannerComponent.prototype.calculateDimensions = function (width, height) { var ratio = width / height; var maxWidth = this.screenDimensions.width > this.maxPreviewWidth ? this.maxPreviewWidth : this.screenDimensions.width - 40; var maxHeight = this.screenDimensions.height - 240; var calculated = { width: maxWidth, height: Math.round(maxWidth / ratio), ratio: ratio }; if (calculated.height > maxHeight) { calculated.height = maxHeight; calculated.width = Math.round(maxHeight * ratio); } return calculated; }; /** * returns a point by it's roles * @param roles - an array of roles by which the point will be fetched */ NgxDocScannerComponent.prototype.getPoint = function (roles) { var _this = this; return this.points.find(function (point) { return _this.limitsService.compareArray(point.roles, roles); }); }; NgxDocScannerComponent.ctorParameters = function () { return [ { type: NgxOpenCVService }, { type: LimitsService }, { type: MatBottomSheet } ]; }; __decorate([ ViewChild('PreviewCanvas', { read: ElementRef, static: true }), __metadata("design:type", ElementRef) ], NgxDocScannerComponent.prototype, "previewCanvas", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], NgxDocScannerComponent.prototype, "exitEditor", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], NgxDocScannerComponent.prototype, "editResult", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], NgxDocScannerComponent.prototype, "error", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], NgxDocScannerComponent.prototype, "ready", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], NgxDocScannerComponent.prototype, "processing", void 0); __decorate([ Input(), __metadata("design:type", File), __metadata("design:paramtypes", [File]) ], NgxDocScannerComponent.prototype, "file", null); __decorate([ Input(), __metadata("design:type", Object) ], NgxDocScannerComponent.prototype, "config", void 0); NgxDocScannerComponent = __decorate([ Component({ selector: 'ngx-doc-scanner', template: "<div [ngStyle]=\"editorStyle\" fxLayoutAlign=\"space-around\" style=\"direction: ltr !important\">\r\n <div #imageContainer [ngStyle]=\"imageDivStyle\" style=\"margin: auto;\" >\r\n <ng-container *ngIf=\"imageLoaded && mode === 'crop'\">\r\n <ngx-shape-outine #shapeOutline [color]=\"options.cropToolColor\" [weight]=\"options.cropToolLineWeight\" [dimensions]=\"previewDimensions\"></ngx-shape-outine>\r\n <ngx-draggable-point #topLeft [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: 0, y: 0}\" [limitRoles]=\"['top', 'left']\" [container]=\"imageContainer\"></ngx-draggable-point>\r\n <ngx-draggable-point #topRight [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: previewDimensions.width, y: 0}\" [limitRoles]=\"['top', 'right']\" [container]=\"imageContainer\"></ngx-draggable-point>\r\n <ngx-draggable-point #bottomLeft [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: 0, y: previewDimensions.height}\" [limitRoles]=\"['bottom', 'left']\" [container]=\"imageContainer\"></ngx-draggable-point>\r\n <ngx-draggable-point #bottomRight [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: previewDimensions.width, y: previewDimensions.height}\" [limitRoles]=\"['bottom', 'right']\" [container]=\"imageContainer\"></ngx-draggable-point>\r\n </ng-container>\r\n <canvas #PreviewCanvas [ngStyle]=\"{'max-width': options.maxPreviewWidth}\" style=\"z-index: 5\" ></canvas>\r\n </div>\r\n <div class=\"editor-actions\" fxLayout=\"row\" fxLayoutAlign=\"space-around\" style=\"position: absolute; bottom: 0; width: 100vw\">\r\n <ng-container *ngFor=\"let button of displayedButtons\" [ngSwitch]=\"button.type\">\r\n <button mat-mini-fab *ngSwitchCase=\"'fab'\" [name]=\"button.name\" (click)=\"button.action()\" [color]=\"options.buttonThemeColor\">\r\n <mat-icon>{{button.icon}}</mat-icon>\r\n </button>\r\n <button mat-raised-button *ngSwitchCase=\"'button'\" [name]=\"button.name\" (click)=\"button.action()\" [color]=\"options.buttonThemeColor\">\r\n <mat-icon>{{button.icon}}</mat-icon>\r\n <span>{{button.text}}}</span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</div>\r\n\r\n\r\n", styles: [".editor-actions{padding:12px}.editor-actions button{margin:5px}"] }), __metadata("design:paramtypes", [NgxOpenCVService, LimitsService, MatBottomSheet]) ], NgxDocScannerComponent); return NgxDocScannerComponent; }()); export { NgxDocScannerComponent }; /** * a class for generating configuration objects for the editor */ var ImageEditorConfig = /** @class */ (function () { function ImageEditorConfig(options) { var _this = this; /** * max dimensions of oputput image. if set to zero */ this.maxImageDimensions = { width: 800, height: 1200 }; /** * background color of the main editor div */ this.editorBackgroundColor = '#fefefe'; /** * css properties for the main editor div */ this.editorDimensions = { width: '100vw', height: '100vh' }; /** * css that will be added to the main div of the editor component */ this.extraCss = { position: 'absolute', top: 0, left: 0 }; /** * material design theme color name */ this.buttonThemeColor = 'accent'; /** * icon for the button that completes the editing and emits the edited image */ this.exportImageIcon = 'cloud_upload'; /** * color of the crop tool */ this.cropToolColor = '#3cabe2'; /** * shape of the crop tool, can be either a rectangle or a circle */ this.cropToolShape = 'rect'; /** * dimensions of the crop tool */ this.cropToolDimensions = { width: 10, height: 10 }; /** * crop tool outline width */ this.cropToolLineWeight = 3; /** * maximum size of the preview pane */ this.maxPreviewWidth = 800; if (options) { Object.keys(options).forEach(function (key) { _this[key] = options[key]; }); } this.editorStyle = { 'background-color': this.editorBackgroundColor }; Object.assign(this.editorStyle, this.editorDimensions); Object.assign(this.editorStyle, this.extraCss); this.pointOptions = { shape: this.cropToolShape, color: this.cropToolColor, width: 0, height: 0 }; Object.assign(this.pointOptions, this.cropToolDimensions); } return ImageEditorConfig; }()); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWRvYy1zY2FubmVyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiJuZzovL25neC1kb2N1bWVudC1zY2FubmVyLyIsInNvdXJjZXMiOlsibGliL2NvbXBvbmVudHMvaW1hZ2UtZWRpdG9yL25neC1kb2Mtc2Nhbm5lci5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQVUsTUFBTSxFQUFFLFNBQVMsRUFBQyxNQUFNLGVBQWUsQ0FBQztBQUNwRyxPQUFPLEVBQUMsYUFBYSxFQUFFLG1CQUFtQixFQUFFLGtCQUFrQixFQUFFLFVBQVUsRUFBQyxNQUFNLCtCQUErQixDQUFDO0FBQ2pILE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRSxPQUFPLEVBQUMsc0JBQXNCLEVBQUMsTUFBTSwwQ0FBMEMsQ0FBQztBQUtoRixPQUFPLEVBQUMsZ0JBQWdCLEVBQUMsTUFBTSxZQUFZLENBQUM7QUFTNUM7SUF5TUUsZ0NBQW9CLFNBQTJCLEVBQVUsYUFBNEIsRUFBVSxXQUEyQjtRQUExSCxpQkF1QkM7UUF2Qm1CLGNBQVMsR0FBVCxTQUFTLENBQWtCO1FBQVUsa0JBQWEsR0FBYixhQUFhLENBQWU7UUFBVSxnQkFBVyxHQUFYLFdBQVcsQ0FBZ0I7UUFwTTFILG1CQUFtQjtRQUNuQixtQkFBbUI7UUFDbkIsbUJBQW1CO1FBQ25COztXQUVHO1FBQ0ssa0JBQWEsR0FBOEI7WUFDakQ7Z0JBQ0UsSUFBSSxFQUFFLE1BQU07Z0JBQ1osTUFBTSxFQUFFO29CQUNOLEtBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUNuQyxDQUFDO2dCQUNELElBQUksRUFBRSxZQUFZO2dCQUNsQixJQUFJLEVBQUUsS0FBSztnQkFDWCxJQUFJLEVBQUUsTUFBTTthQUNiO1lBQ0Q7Z0JBQ0UsSUFBSSxFQUFFLFFBQVE7Z0JB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