UNPKG

ngx-document-scanner

Version:

Angular 2+ component for cropping and enhancing images of documents

1,101 lines (1,068 loc) 82.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('@angular/material/bottom-sheet'), require('ngx-opencv'), require('@angular/flex-layout'), require('@angular/material/button'), require('@angular/material/icon'), require('@angular/material/list'), require('angular2-draggable'), require('@angular/common')) : typeof define === 'function' && define.amd ? define('ngx-document-scanner', ['exports', '@angular/core', 'rxjs', '@angular/material/bottom-sheet', 'ngx-opencv', '@angular/flex-layout', '@angular/material/button', '@angular/material/icon', '@angular/material/list', 'angular2-draggable', '@angular/common'], factory) : (global = global || self, factory(global['ngx-document-scanner'] = {}, global.ng.core, global.rxjs, global.ng.material.bottomSheet, global.ngxOpencv, global.ng.flexLayout, global.ng.material.button, global.ng.material.icon, global.ng.material.list, global.angular2Draggable, global.ng.common)); }(this, (function (exports, core, rxjs, bottomSheet, ngxOpencv, flexLayout, button, icon, list, angular2Draggable, common) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __param(paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } } function __metadata(metadataKey, metadataValue) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __createBinding(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; } function __exportStar(m, exports) { for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) exports[p] = m[p]; } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } function __spread() { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; } function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncDelegator(o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; function __importStar(mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result.default = mod; return result; } function __importDefault(mod) { return (mod && mod.__esModule) ? mod : { default: mod }; } function __classPrivateFieldGet(receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); } function __classPrivateFieldSet(receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; } var LimitsService = /** @class */ (function () { function LimitsService() { this.limitDirections = ['left', 'right', 'top', 'bottom']; /** * stores the crop limits limits */ this._limits = { top: 0, bottom: 0, right: 0, left: 0 }; /** * stores the array of the draggable points displayed on the crop area */ this._points = []; // *********** // // Observables // // *********** // this.positions = new rxjs.BehaviorSubject(Array.from(this._points)); this.repositionEvent = new rxjs.BehaviorSubject([]); this.limits = new rxjs.BehaviorSubject(this._limits); this.paneDimensions = new rxjs.BehaviorSubject({ width: 0, height: 0 }); } /** * set privew pane dimensions */ LimitsService.prototype.setPaneDimensions = function (dimensions) { var _this = this; return new Promise(function (resolve, reject) { _this._paneDimensions = dimensions; _this.paneDimensions.next(dimensions); resolve(); }); }; /** * repositions points externally */ LimitsService.prototype.repositionPoints = function (positions) { var _this = this; this._points = positions; positions.forEach(function (position) { _this.positionChange(position); }); this.repositionEvent.next(positions); }; /** * updates limits and point positions and calls next on the observables * @param positionChangeData - position change event data */ LimitsService.prototype.positionChange = function (positionChangeData) { var _this = this; // update positions according to current position change this.updatePosition(positionChangeData); // for each direction: // 1. filter the _points that have a role as the direction's limit // 2. for top and left find max x | y values, and min for right and bottom this.limitDirections.forEach(function (direction) { var relevantPoints = _this._points.filter(function (point) { return point.roles.includes(direction); }) .map(function (point) { return point[_this.getDirectionAxis(direction)]; }); var limit; if (direction === 'top' || direction === 'left') { limit = Math.max.apply(Math, __spread(relevantPoints)); } if (direction === 'right' || direction === 'bottom') { limit = Math.min.apply(Math, __spread(relevantPoints)); } _this._limits[direction] = limit; }); this.limits.next(this._limits); this.positions.next(Array.from(this._points)); }; /** * updates the position of the point * @param positionChange - position change event data */ LimitsService.prototype.updatePosition = function (positionChange) { var _this = this; // finds the current position of the point by it's roles, than splices it for the new position or pushes it if it's not yet in the array var index = this._points.findIndex(function (point) { return _this.compareArray(positionChange.roles, point.roles); }); if (index === -1) { this._points.push(positionChange); } else { this._points.splice(index, 1, positionChange); } }; /** * check if a position change event exceeds the limits * @param positionChange - position change event data * @returns LimitException0 */ LimitsService.prototype.exceedsLimit = function (positionChange) { var _this = this; var pointLimits = this.limitDirections.filter(function (direction) { return !positionChange.roles.includes(direction); }); var limitException = { exceeds: false, resetCoefficients: { x: 0, y: 0 }, resetCoordinates: { x: positionChange.x, y: positionChange.y } }; // limit directions are the opposite sides of the point's roles pointLimits.forEach(function (direction) { var directionAxis = _this.getDirectionAxis(direction); if (direction === 'top' || direction === 'left') { if (positionChange[directionAxis] < _this._limits[direction]) { limitException.resetCoefficients[directionAxis] = 1; limitException.resetCoordinates[directionAxis] = _this._limits[direction]; } } else if (direction === 'right' || direction === 'bottom') { if (positionChange[directionAxis] > _this._limits[direction]) { limitException.resetCoefficients[directionAxis] = -1; limitException.resetCoordinates[directionAxis] = _this._limits[direction]; } } }); if (limitException.resetCoefficients.x !== 0 || limitException.resetCoefficients.y !== 0) { limitException.exceeds = true; } return limitException; }; /** * rotate crop tool points clockwise * @param resizeRatios - ratio between the new dimensions and the previous * @param initialPreviewDimensions - preview pane dimensions before rotation * @param initialPositions - current positions before rotation */ LimitsService.prototype.rotateClockwise = function (resizeRatios, initialPreviewDimensions, initialPositions) { var _this = this; // convert positions to ratio between position to initial pane dimension initialPositions = initialPositions.map(function (point) { return new PositionChangeData({ x: point.x / initialPreviewDimensions.width, y: point.y / initialPreviewDimensions.height, }, point.roles); }); this.repositionPoints(initialPositions.map(function (point) { return _this.rotateCornerClockwise(point); })); }; /** * returns the corner positions after a 90 degrees clockwise rotation */ LimitsService.prototype.rotateCornerClockwise = function (corner) { var _this = this; var rotated = { x: this._paneDimensions.width * (1 - corner.y), y: this._paneDimensions.height * corner.x, roles: [] }; // rotates corner according to order var order = [ ['bottom', 'left'], ['top', 'left'], ['top', 'right'], ['bottom', 'right'], ['bottom', 'left'] ]; rotated.roles = order[order.findIndex(function (roles) { return _this.compareArray(roles, corner.roles); }) + 1]; return rotated; }; /** * checks if two array contain the same values * @param array1 - array 1 * @param array2 - array 2 * @returns boolean */ LimitsService.prototype.compareArray = function (array1, array2) { return array1.every(function (element) { return array2.includes(element); }) && array1.length === array2.length; }; LimitsService.prototype.getDirectionAxis = function (direction) { return { left: 'x', right: 'x', top: 'y', bottom: 'y' }[direction]; }; LimitsService.ɵprov = core.ɵɵdefineInjectable({ factory: function LimitsService_Factory() { return new LimitsService(); }, token: LimitsService, providedIn: "root" }); LimitsService = __decorate([ core.Injectable({ providedIn: 'root' }), __metadata("design:paramtypes", []) ], LimitsService); return LimitsService; }()); var PositionChangeData = /** @class */ (function () { function PositionChangeData(position, roles) { this.x = position.x; this.y = position.y; this.roles = roles; } return PositionChangeData; }()); var NgxDraggablePointComponent = /** @class */ (function () { function NgxDraggablePointComponent(limitsService) { this.limitsService = limitsService; this.width = 10; this.height = 10; this.color = '#3cabe2'; this.shape = 'rect'; this.pointOptions = 'rect'; this.position = { x: 0, y: 0 }; } NgxDraggablePointComponent.prototype.ngAfterViewInit = function () { var _this = this; Object.keys(this.pointOptions).forEach(function (key) { _this[key] = _this.pointOptions[key]; }); // subscribe to pane dimensions changes this.limitsService.paneDimensions.subscribe(function (dimensions) { if (dimensions.width > 0 && dimensions.width > 0) { _this._paneDimensions = { width: dimensions.width, height: dimensions.height }; _this.position = _this.getInitialPosition(dimensions); _this.limitsService.positionChange(new PositionChangeData(_this.position, _this.limitRoles)); } }); // subscribe to external reposition events this.limitsService.repositionEvent.subscribe(function (positions) { if (positions.length > 0) { _this.externalReposition(positions); } }); }; /** * returns a css style object for the point */ NgxDraggablePointComponent.prototype.pointStyle = function () { return { width: this.width + 'px', height: this.height + 'px', 'background-color': this.color, 'border-radius': this.shape === 'circle' ? '100%' : 0, position: 'absolute' }; }; /** * registers a position change on the limits service, and adjusts position if necessary * @param position - the current position of the point */ NgxDraggablePointComponent.prototype.positionChange = function (position) { var positionChangeData = new PositionChangeData(position, this.limitRoles); var limitException = this.limitsService.exceedsLimit(positionChangeData); if (limitException.exceeds) { // if exceeds limits, reposition this.resetPosition = limitException.resetCoordinates; } else { this.limitsService.positionChange(positionChangeData); this._currentPosition = position; } }; /** * adjusts the position of the point after a limit exception */ NgxDraggablePointComponent.prototype.adjustPosition = function (limitException) { var newPosition = { x: 0, y: 0 }; Object.keys(this.startPosition).forEach(function (axis) { newPosition[axis] = limitException.resetCoordinates[axis] + limitException.resetCoefficients[axis]; }); this.position = newPosition; this.limitsService.positionChange(new PositionChangeData(this.position, this.limitRoles)); }; /** * called on movement end, checks if last position exceeded the limits ad adjusts */ NgxDraggablePointComponent.prototype.movementEnd = function (position) { var positionChangeData = new PositionChangeData(position, this.limitRoles); var limitException = this.limitsService.exceedsLimit(positionChangeData); if (limitException.exceeds) { this.resetPosition = limitException.resetCoordinates; if (limitException.exceeds) { this.adjustPosition(limitException); positionChangeData = new PositionChangeData(this.position, this.limitRoles); this.limitsService.updatePosition(positionChangeData); } } }; /** * calculates the initial positions of the point by it's roles * @param dimensions - dimensions of the pane in which the point is located */ NgxDraggablePointComponent.prototype.getInitialPosition = function (dimensions) { return { x: this.limitRoles.includes('left') ? 0 : dimensions.width - this.width / 2, y: this.limitRoles.includes('top') ? 0 : dimensions.height - this.height / 2 }; }; /** * repositions the point after an external reposition event * @param positions - an array of all points on the pane */ NgxDraggablePointComponent.prototype.externalReposition = function (positions) { var _this = this; positions.forEach(function (position) { if (_this.limitsService.compareArray(_this.limitRoles, position.roles)) { position = _this.enforcePaneLimits(position); _this.position = { x: position.x, y: position.y }; } }); }; /** * returns a new point position if the movement exceeded the pane limit */ NgxDraggablePointComponent.prototype.enforcePaneLimits = function (position) { if (this._paneDimensions.width === 0 || this._paneDimensions.height === 0) { return position; } else { if (position.x > this._paneDimensions.width) { position.x = this._paneDimensions.width; } if (position.x < 0) { position.x = 1; } if (position.y > this._paneDimensions.height) { position.y = this._paneDimensions.height; } if (position.y < 0) { position.y = 1; } } return position; }; NgxDraggablePointComponent.ctorParameters = function () { return [ { type: LimitsService } ]; }; __decorate([ core.Input(), __metadata("design:type", Object) ], NgxDraggablePointComponent.prototype, "width", void 0); __decorate([ core.Input(), __metadata("design:type", Object) ], NgxDraggablePointComponent.prototype, "height", void 0); __decorate([ core.Input(), __metadata("design:type", Object) ], NgxDraggablePointComponent.prototype, "color", void 0); __decorate([ core.Input(), __metadata("design:type", String) ], NgxDraggablePointComponent.prototype, "shape", void 0); __decorate([ core.Input(), __metadata("design:type", String) ], NgxDraggablePointComponent.prototype, "pointOptions", void 0); __decorate([ core.Input(), __metadata("design:type", Array) ], NgxDraggablePointComponent.prototype, "limitRoles", void 0); __decorate([ core.Input(), __metadata("design:type", Object) ], NgxDraggablePointComponent.prototype, "startPosition", void 0); __decorate([ core.Input(), __metadata("design:type", HTMLElement) ], NgxDraggablePointComponent.prototype, "container", void 0); __decorate([ core.Input(), __metadata("design:type", Object) ], NgxDraggablePointComponent.prototype, "_currentPosition", void 0); NgxDraggablePointComponent = __decorate([ core.Component({ selector: 'ngx-draggable-point', template: "<div #point ngDraggable=\"draggable\"\r\n (movingOffset)=\"positionChange($event)\"\r\n [ngStyle]=\"pointStyle()\"\r\n [position]=\"position\"\r\n [bounds]=\"container\"\r\n [inBounds]=\"true\"\r\n (endOffset)=\"movementEnd($event)\"\r\n style=\"z-index: 1000\">\r\n</div>\r\n" }), __metadata("design:paramtypes", [LimitsService]) ], NgxDraggablePointComponent); return NgxDraggablePointComponent; }()); var NgxFilterMenuComponent = /** @class */ (function () { function NgxFilterMenuComponent(bottomSheetRef, data) { var _this = this; this.bottomSheetRef = bottomSheetRef; this.data = data; this.filterOptions = [ { name: 'default', icon: 'filter_b_and_w', action: function (filter) { _this.filterSelected.emit(filter); }, text: 'B&W' }, { name: 'bw2', icon: 'filter_b_and_w', action: function (filter) { _this.filterSelected.emit(filter); }, text: 'B&W 2' }, { name: 'bw3', icon: 'blur_on', action: function (filter) { _this.filterSelected.emit(filter); }, text: 'B&W 3' }, { name: 'magic_color', icon: 'filter_vintage', action: function (filter) { _this.filterSelected.emit(filter); }, text: 'Magic Color' }, { name: 'original', icon: 'crop_original', action: function (filter) { _this.filterSelected.emit(filter); }, text: 'Original' }, ]; this.filterSelected = new core.EventEmitter(); } NgxFilterMenuComponent.prototype.selectOption = function (optionName) { this.data.filter = optionName; this.bottomSheetRef.dismiss(); }; NgxFilterMenuComponent.ctorParameters = function () { return [ { type: bottomSheet.MatBottomSheetRef }, { type: undefined, decorators: [{ type: core.Inject, args: [bottomSheet.MAT_BOTTOM_SHEET_DATA,] }] } ]; }; __decorate([ core.Output(), __metadata("design:type", core.EventEmitter) ], NgxFilterMenuComponent.prototype, "filterSelected", void 0); NgxFilterMenuComponent = __decorate([ core.Component({ selector: 'ngx-filter-menu', template: "<mat-action-list>\r\n <button mat-list-item *ngFor=\"let option of filterOptions\" (click)=\"selectOption(option.name)\">\r\n <mat-icon>{{option.icon}}</mat-icon>\r\n <span fxFlex=\"100\" style=\"text-align: start; margin: 5px\">{{option.text}}</span>\r\n <span fxFlex=\"100\"></span>\r\n <mat-icon *ngIf=\"option.name === data.filter\">done</mat-icon>\r\n </button>\r\n</mat-action-list>\r\n" }), __param(1, core.Inject(bottomSheet.MAT_BOTTOM_SHEET_DATA)), __metadata("design:paramtypes", [bottomSheet.MatBottomSheetRef, Object]) ], NgxFilterMenuComponent); return NgxFilterMenuComponent; }()); var NgxShapeOutlineComponent = /** @class */ (function () { function NgxShapeOutlineComponent(limitsService) { this.limitsService = limitsService; this.color = '#3cabe2'; } NgxShapeOutlineComponent.prototype.ngAfterViewInit = function () { var _this = this; // init drawing canvas dimensions this.canvas.nativeElement.width = this.dimensions.width; this.canvas.nativeElement.height = this.dimensions.height; this.limitsService.positions.subscribe(function (positions) { if (positions.length === 4) { _this._points = positions; _this.sortPoints(); _this.clearCanvas(); _this.drawShape(); } }); // subscribe to changes in the pane's dimensions this.limitsService.paneDimensions.subscribe(function (dimensions) { _this.clearCanvas(); _this.canvas.nativeElement.width = dimensions.width; _this.canvas.nativeElement.height = dimensions.height; }); // subscribe to reposition events this.limitsService.repositionEvent.subscribe(function (positions) { if (positions.length === 4) { setTimeout(function () { _this.clearCanvas(); _this.sortPoints(); _this.drawShape(); }, 10); } }); }; /** * clears the shape canvas */ NgxShapeOutlineComponent.prototype.clearCanvas = function () { var canvas = this.canvas.nativeElement; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, this.dimensions.width, this.dimensions.height); }; /** * sorts the array of points according to their clockwise alignment */ NgxShapeOutlineComponent.prototype.sortPoints = function () { var _this = this; var _points = Array.from(this._points); var sortedPoints = []; var sortOrder = { vertical: ['top', 'top', 'bottom', 'bottom'], horizontal: ['left', 'right', 'right', 'left'] }; var _loop_1 = function (i) { var roles = Array.from([sortOrder.vertical[i], sortOrder.horizontal[i]]); sortedPoints.push(_points.filter(function (point) { return _this.limitsService.compareArray(point.roles, roles); })[0]); }; for (var i = 0; i < 4; i++) { _loop_1(i); } this._sortedPoints = sortedPoints; }; /** * draws a line between the points according to their order */ NgxShapeOutlineComponent.prototype.drawShape = function () { var _this = this; var canvas = this.canvas.nativeElement; var ctx = canvas.getContext('2d'); ctx.lineWidth = this.weight; ctx.strokeStyle = this.color; ctx.beginPath(); this._sortedPoints.forEach(function (point, index) { if (index === 0) { ctx.moveTo(point.x, point.y); } if (index !== _this._sortedPoints.length - 1) { var nextPoint = _this._sortedPoints[index + 1]; ctx.lineTo(nextPoint.x, nextPoint.y); } else { ctx.closePath(); } }); ctx.stroke(); }; NgxShapeOutlineComponent.ctorParameters = function () { return [ { type: LimitsService } ]; }; __decorate([ core.Input(), __metadata("design:type", Object) ], NgxShapeOutlineComponent.prototype, "color", void 0); __decorate([ core.Input(), __metadata("design:type", Number) ], NgxShapeOutlineComponent.prototype, "weight", void 0); __decorate([ core.Input(), __metadata("design:type", Object) ], NgxShapeOutlineComponent.prototype, "dimensions", void 0); __decorate([ core.ViewChild('outline'), __metadata("design:type", Object) ], NgxShapeOutlineComponent.prototype, "canvas", void 0); NgxShapeOutlineComponent = __decorate([ core.Component({ selector: 'ngx-shape-outine', template: "<canvas #outline\r\n style=\"position: absolute; z-index: 1000\"\r\n [ngStyle]=\"{width: dimensions.width + 'px', height: dimensions.height + 'px'}\"\r\n *ngIf=\"dimensions\">\r\n</canvas>\r\n" }), __metadata("design:paramtypes", [LimitsService]) ], NgxShapeOutlineComponent); return NgxShapeOutlineComponent; }()); 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 core.EventEmitter(); /** * fires on edit completion */ this.editResult = new core.EventEmitter(); /** * emits errors, can be linked to an error handler of choice */ this.error = new core.EventEmitter(); /** * emits the loading status of the cv module. */ this.ready = new core.EventEmitter(); /** * emits true when processing is done, false when completed */ this.processing = new core.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();