@autonomdev/ngx-document-scanner
Version:
Angular 2+ component for cropping and enhancing images of documents
2,244 lines • 74.2 kB
JavaScript
import { Injectable, ɵɵdefineInjectable, Component, Input, EventEmitter, Inject, Output, ViewChild, ElementRef, NgModule } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { MatBottomSheetRef, MAT_BOTTOM_SHEET_DATA, MatBottomSheet, MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { __awaiter } from 'tslib';
import { NgxOpenCVService, OpenCvConfigToken, NgxOpenCVModule } from 'ngx-opencv';
import { FlexLayoutModule } from '@angular/flex-layout';
import { AngularDraggableModule } from 'angular2-draggable';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
/**
* @fileoverview added by tsickle
* Generated from: lib/services/limits.service.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class LimitsService {
constructor() {
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 BehaviorSubject(Array.from(this._points));
this.repositionEvent = new BehaviorSubject([]);
this.limits = new BehaviorSubject(this._limits);
this.paneDimensions = new BehaviorSubject({ width: 0, height: 0 });
}
/**
* set privew pane dimensions
* @param {?} dimensions
* @return {?}
*/
setPaneDimensions(dimensions) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
this._paneDimensions = dimensions;
this.paneDimensions.next(dimensions);
resolve();
}));
}
/**
* repositions points externally
* @param {?} positions
* @return {?}
*/
repositionPoints(positions) {
this._points = positions;
positions.forEach((/**
* @param {?} position
* @return {?}
*/
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
* @return {?}
*/
positionChange(positionChangeData) {
// 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((/**
* @param {?} direction
* @return {?}
*/
direction => {
/** @type {?} */
const relevantPoints = this._points.filter((/**
* @param {?} point
* @return {?}
*/
point => {
return point.roles.includes(direction);
}))
.map((/**
* @param {?} point
* @return {?}
*/
(point) => {
return point[this.getDirectionAxis(direction)];
}));
/** @type {?} */
let limit;
if (direction === 'top' || direction === 'left') {
limit = Math.max(...relevantPoints);
}
if (direction === 'right' || direction === 'bottom') {
limit = Math.min(...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
* @return {?}
*/
updatePosition(positionChange) {
// 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
/** @type {?} */
const index = this._points.findIndex((/**
* @param {?} point
* @return {?}
*/
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
* @return {?} LimitException0
*/
exceedsLimit(positionChange) {
/** @type {?} */
const pointLimits = this.limitDirections.filter((/**
* @param {?} direction
* @return {?}
*/
direction => {
return !positionChange.roles.includes(direction);
}));
/** @type {?} */
const 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((/**
* @param {?} direction
* @return {?}
*/
direction => {
/** @type {?} */
const 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
* @return {?}
*/
rotateClockwise(resizeRatios, initialPreviewDimensions, initialPositions) {
// convert positions to ratio between position to initial pane dimension
initialPositions = initialPositions.map((/**
* @param {?} point
* @return {?}
*/
point => {
return new PositionChangeData({
x: point.x / initialPreviewDimensions.width,
y: point.y / initialPreviewDimensions.height,
}, point.roles);
}));
this.repositionPoints(initialPositions.map((/**
* @param {?} point
* @return {?}
*/
point => {
return this.rotateCornerClockwise(point);
})));
}
/**
* returns the corner positions after a 90 degrees clockwise rotation
* @private
* @param {?} corner
* @return {?}
*/
rotateCornerClockwise(corner) {
/** @type {?} */
const rotated = {
x: this._paneDimensions.width * (1 - corner.y),
y: this._paneDimensions.height * corner.x,
roles: []
};
// rotates corner according to order
/** @type {?} */
const order = [
['bottom', 'left'],
['top', 'left'],
['top', 'right'],
['bottom', 'right'],
['bottom', 'left']
];
rotated.roles = order[order.findIndex((/**
* @param {?} roles
* @return {?}
*/
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
* @return {?} boolean
*/
compareArray(array1, array2) {
return array1.every((/**
* @param {?} element
* @return {?}
*/
(element) => {
return array2.includes(element);
})) && array1.length === array2.length;
}
/**
* @private
* @param {?} direction
* @return {?}
*/
getDirectionAxis(direction) {
return {
left: 'x',
right: 'x',
top: 'y',
bottom: 'y'
}[direction];
}
}
LimitsService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
/** @nocollapse */
LimitsService.ctorParameters = () => [];
/** @nocollapse */ LimitsService.ɵprov = ɵɵdefineInjectable({ factory: function LimitsService_Factory() { return new LimitsService(); }, token: LimitsService, providedIn: "root" });
if (false) {
/**
* @type {?}
* @private
*/
LimitsService.prototype.limitDirections;
/**
* stores the crop limits limits
* @type {?}
* @private
*/
LimitsService.prototype._limits;
/**
* stores the array of the draggable points displayed on the crop area
* @type {?}
* @private
*/
LimitsService.prototype._points;
/**
* stores the pane dimensions
* @type {?}
* @private
*/
LimitsService.prototype._paneDimensions;
/** @type {?} */
LimitsService.prototype.positions;
/** @type {?} */
LimitsService.prototype.repositionEvent;
/** @type {?} */
LimitsService.prototype.limits;
/** @type {?} */
LimitsService.prototype.paneDimensions;
}
/**
* @record
*/
function PointPositionChange() { }
if (false) {
/** @type {?} */
PointPositionChange.prototype.x;
/** @type {?} */
PointPositionChange.prototype.y;
/** @type {?} */
PointPositionChange.prototype.roles;
}
/**
* @record
*/
function AreaLimits() { }
if (false) {
/** @type {?} */
AreaLimits.prototype.top;
/** @type {?} */
AreaLimits.prototype.bottom;
/** @type {?} */
AreaLimits.prototype.right;
/** @type {?} */
AreaLimits.prototype.left;
}
class PositionChangeData {
/**
* @param {?} position
* @param {?} roles
*/
constructor(position, roles) {
this.x = position.x;
this.y = position.y;
this.roles = roles;
}
}
if (false) {
/** @type {?} */
PositionChangeData.prototype.x;
/** @type {?} */
PositionChangeData.prototype.y;
/** @type {?} */
PositionChangeData.prototype.roles;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/components/draggable-point/ngx-draggable-point.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxDraggablePointComponent {
/**
* @param {?} limitsService
*/
constructor(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
};
}
/**
* @return {?}
*/
ngAfterViewInit() {
Object.keys(this.pointOptions).forEach((/**
* @param {?} key
* @return {?}
*/
key => {
this[key] = this.pointOptions[key];
}));
// subscribe to pane dimensions changes
this.limitsService.paneDimensions.subscribe((/**
* @param {?} dimensions
* @return {?}
*/
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((/**
* @param {?} positions
* @return {?}
*/
positions => {
if (positions.length > 0) {
this.externalReposition(positions);
}
}));
}
/**
* returns a css style object for the point
* @return {?}
*/
pointStyle() {
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
* @return {?}
*/
positionChange(position) {
/** @type {?} */
const positionChangeData = new PositionChangeData(position, this.limitRoles);
/** @type {?} */
const 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
* @private
* @param {?} limitException
* @return {?}
*/
adjustPosition(limitException) {
/** @type {?} */
const newPosition = {
x: 0,
y: 0
};
Object.keys(this.startPosition).forEach((/**
* @param {?} axis
* @return {?}
*/
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
* @param {?} position
* @return {?}
*/
movementEnd(position) {
/** @type {?} */
let positionChangeData = new PositionChangeData(position, this.limitRoles);
/** @type {?} */
const 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
* @private
* @param {?} dimensions - dimensions of the pane in which the point is located
* @return {?}
*/
getInitialPosition(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
* @private
* @param {?} positions - an array of all points on the pane
* @return {?}
*/
externalReposition(positions) {
positions.forEach((/**
* @param {?} position
* @return {?}
*/
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
* @private
* @param {?} position
* @return {?}
*/
enforcePaneLimits(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.decorators = [
{ type: Component, args: [{
selector: 'ngx-draggable-point',
template: "<div #point ngDraggable=\"draggable\"\n (movingOffset)=\"positionChange($event)\"\n [ngStyle]=\"pointStyle()\"\n [position]=\"position\"\n [bounds]=\"container\"\n [inBounds]=\"true\"\n (endOffset)=\"movementEnd($event)\"\n style=\"z-index: 1000\">\n</div>\n"
}] }
];
/** @nocollapse */
NgxDraggablePointComponent.ctorParameters = () => [
{ type: LimitsService }
];
NgxDraggablePointComponent.propDecorators = {
width: [{ type: Input }],
height: [{ type: Input }],
color: [{ type: Input }],
shape: [{ type: Input }],
pointOptions: [{ type: Input }],
limitRoles: [{ type: Input }],
startPosition: [{ type: Input }],
container: [{ type: Input }],
_currentPosition: [{ type: Input }]
};
if (false) {
/** @type {?} */
NgxDraggablePointComponent.prototype.width;
/** @type {?} */
NgxDraggablePointComponent.prototype.height;
/** @type {?} */
NgxDraggablePointComponent.prototype.color;
/** @type {?} */
NgxDraggablePointComponent.prototype.shape;
/** @type {?} */
NgxDraggablePointComponent.prototype.pointOptions;
/** @type {?} */
NgxDraggablePointComponent.prototype.limitRoles;
/** @type {?} */
NgxDraggablePointComponent.prototype.startPosition;
/** @type {?} */
NgxDraggablePointComponent.prototype.container;
/**
* @type {?}
* @private
*/
NgxDraggablePointComponent.prototype._currentPosition;
/** @type {?} */
NgxDraggablePointComponent.prototype.position;
/**
* @type {?}
* @private
*/
NgxDraggablePointComponent.prototype._paneDimensions;
/** @type {?} */
NgxDraggablePointComponent.prototype.resetPosition;
/**
* @type {?}
* @private
*/
NgxDraggablePointComponent.prototype.limitsService;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/components/filter-menu/ngx-filter-menu.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxFilterMenuComponent {
/**
* @param {?} bottomSheetRef
* @param {?} data
*/
constructor(bottomSheetRef, data) {
this.bottomSheetRef = bottomSheetRef;
this.data = data;
this.filterOptions = [
{
name: 'bw2',
icon: 'filter_b_and_w',
action: (/**
* @param {?} filter
* @return {?}
*/
(filter) => {
this.filterSelected.emit(filter);
}),
text: 'B&W 2'
},
{
name: 'bw3',
icon: 'blur_on',
action: (/**
* @param {?} filter
* @return {?}
*/
(filter) => {
this.filterSelected.emit(filter);
}),
text: 'B&W 3'
},
{
name: 'magic_color',
icon: 'filter_vintage',
action: (/**
* @param {?} filter
* @return {?}
*/
(filter) => {
this.filterSelected.emit(filter);
}),
text: 'Magic Color'
},
{
name: 'default',
icon: 'crop_original',
action: (/**
* @param {?} filter
* @return {?}
*/
(filter) => {
this.filterSelected.emit(filter);
}),
text: 'Original'
},
];
this.filterSelected = new EventEmitter();
}
/**
* @param {?} optionName
* @return {?}
*/
selectOption(optionName) {
this.data.filter = optionName;
this.bottomSheetRef.dismiss();
}
}
NgxFilterMenuComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-filter-menu',
template: "<mat-action-list>\n <button mat-list-item *ngFor=\"let option of filterOptions\" (click)=\"selectOption(option.name)\">\n <mat-icon>{{option.icon}}</mat-icon>\n <span fxFlex=\"100\" style=\"text-align: start; margin: 5px\">{{option.text}}</span>\n <span fxFlex=\"100\"></span>\n <mat-icon *ngIf=\"option.name === data.filter\">done</mat-icon>\n </button>\n</mat-action-list>\n"
}] }
];
/** @nocollapse */
NgxFilterMenuComponent.ctorParameters = () => [
{ type: MatBottomSheetRef },
{ type: undefined, decorators: [{ type: Inject, args: [MAT_BOTTOM_SHEET_DATA,] }] }
];
NgxFilterMenuComponent.propDecorators = {
filterSelected: [{ type: Output }]
};
if (false) {
/** @type {?} */
NgxFilterMenuComponent.prototype.filterOptions;
/** @type {?} */
NgxFilterMenuComponent.prototype.filterSelected;
/**
* @type {?}
* @private
*/
NgxFilterMenuComponent.prototype.bottomSheetRef;
/** @type {?} */
NgxFilterMenuComponent.prototype.data;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/components/shape-outline/ngx-shape-outline.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxShapeOutlineComponent {
/**
* @param {?} limitsService
*/
constructor(limitsService) {
this.limitsService = limitsService;
this.color = '#3cabe2';
}
/**
* @return {?}
*/
ngAfterViewInit() {
// init drawing canvas dimensions
this.canvas.nativeElement.width = this.dimensions.width;
this.canvas.nativeElement.height = this.dimensions.height;
this.limitsService.positions.subscribe((/**
* @param {?} positions
* @return {?}
*/
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((/**
* @param {?} dimensions
* @return {?}
*/
dimensions => {
this.clearCanvas();
this.canvas.nativeElement.width = dimensions.width;
this.canvas.nativeElement.height = dimensions.height;
}));
// subscribe to reposition events
this.limitsService.repositionEvent.subscribe((/**
* @param {?} positions
* @return {?}
*/
positions => {
if (positions.length === 4) {
setTimeout((/**
* @return {?}
*/
() => {
this.clearCanvas();
this.sortPoints();
this.drawShape();
}), 10);
}
}));
}
/**
* clears the shape canvas
* @private
* @return {?}
*/
clearCanvas() {
/** @type {?} */
const canvas = this.canvas.nativeElement;
/** @type {?} */
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, this.dimensions.width, this.dimensions.height);
}
/**
* sorts the array of points according to their clockwise alignment
* @private
* @return {?}
*/
sortPoints() {
/** @type {?} */
const _points = Array.from(this._points);
/** @type {?} */
const sortedPoints = [];
/** @type {?} */
const sortOrder = {
vertical: ['top', 'top', 'bottom', 'bottom'],
horizontal: ['left', 'right', 'right', 'left']
};
for (let i = 0; i < 4; i++) {
/** @type {?} */
const roles = Array.from([sortOrder.vertical[i], sortOrder.horizontal[i]]);
sortedPoints.push(_points.filter((/**
* @param {?} point
* @return {?}
*/
(point) => {
return this.limitsService.compareArray(point.roles, roles);
}))[0]);
}
this._sortedPoints = sortedPoints;
}
/**
* draws a line between the points according to their order
* @private
* @return {?}
*/
drawShape() {
/** @type {?} */
const canvas = this.canvas.nativeElement;
/** @type {?} */
const ctx = canvas.getContext('2d');
ctx.lineWidth = this.weight;
ctx.strokeStyle = this.color;
ctx.beginPath();
this._sortedPoints.forEach((/**
* @param {?} point
* @param {?} index
* @return {?}
*/
(point, index) => {
if (index === 0) {
ctx.moveTo(point.x, point.y);
}
if (index !== this._sortedPoints.length - 1) {
/** @type {?} */
const nextPoint = this._sortedPoints[index + 1];
ctx.lineTo(nextPoint.x, nextPoint.y);
}
else {
ctx.closePath();
}
}));
ctx.stroke();
}
}
NgxShapeOutlineComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-shape-outine',
template: "<canvas #outline\n style=\"position: absolute; z-index: 1000\"\n [ngStyle]=\"{width: dimensions.width + 'px', height: dimensions.height + 'px'}\"\n *ngIf=\"dimensions\">\n</canvas>\n"
}] }
];
/** @nocollapse */
NgxShapeOutlineComponent.ctorParameters = () => [
{ type: LimitsService }
];
NgxShapeOutlineComponent.propDecorators = {
color: [{ type: Input }],
weight: [{ type: Input }],
dimensions: [{ type: Input }],
canvas: [{ type: ViewChild, args: ['outline',] }]
};
if (false) {
/** @type {?} */
NgxShapeOutlineComponent.prototype.color;
/** @type {?} */
NgxShapeOutlineComponent.prototype.weight;
/** @type {?} */
NgxShapeOutlineComponent.prototype.dimensions;
/** @type {?} */
NgxShapeOutlineComponent.prototype.canvas;
/**
* @type {?}
* @private
*/
NgxShapeOutlineComponent.prototype._points;
/**
* @type {?}
* @private
*/
NgxShapeOutlineComponent.prototype._sortedPoints;
/**
* @type {?}
* @private
*/
NgxShapeOutlineComponent.prototype.limitsService;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/components/image-editor/ngx-doc-scanner.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxDocScannerComponent {
/**
* @param {?} ngxOpenCv
* @param {?} limitsService
* @param {?} bottomSheet
*/
constructor(ngxOpenCv, limitsService, bottomSheet) {
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: (/**
* @return {?}
*/
() => {
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: (/**
* @return {?}
*/
() => __awaiter(this, void 0, void 0, function* () {
this.mode = 'color';
yield this.transform();
yield this.applyFilter(true);
})),
icon: 'done',
type: 'fab',
mode: 'crop'
},
{
name: 'back',
action: (/**
* @return {?}
*/
() => {
this.mode = 'crop';
this.loadFile(this.originalImage);
}),
icon: 'arrow_back',
type: 'fab',
mode: 'color'
},
{
name: 'filter',
action: (/**
* @return {?}
*/
() => {
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((/**
* @param {?} cvState
* @return {?}
*/
(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((/**
* @param {?} points
* @return {?}
*/
points => {
this.points = points;
}));
}
/**
* returns an array of buttons according to the editor mode
* @return {?}
*/
get displayedButtons() {
return this.editorButtons.filter((/**
* @param {?} button
* @return {?}
*/
button => {
return button.mode === this.mode;
}));
}
// ****** //
// INPUTS //
// ****** //
/**
* set image for editing
* @param {?} file - file from form input
* @return {?}
*/
set file(file) {
if (file) {
setTimeout((/**
* @return {?}
*/
() => {
this.processing.emit(true);
}), 5);
this.imageLoaded = false;
this.originalImage = file;
this.ngxOpenCv.cvState.subscribe((/**
* @param {?} cvState
* @return {?}
*/
(cvState) => __awaiter(this, void 0, void 0, function* () {
if (cvState.ready) {
// read file to image & canvas
yield this.loadFile(file);
this.processing.emit(false);
}
})));
}
}
/**
* @return {?}
*/
ngOnInit() {
// set options from config object
this.options = new ImageEditorConfig(this.config);
// set export image icon
this.editorButtons.forEach((/**
* @param {?} button
* @return {?}
*/
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
* @return {?}
*/
exit() {
this.exitEditor.emit('canceled');
}
/**
* applies the selected filter, and when done emits the resulted image
* @private
* @return {?}
*/
exportImage() {
return __awaiter(this, void 0, void 0, function* () {
yield this.applyFilter(false);
if (this.options.maxImageDimensions) {
this.resize(this.editedImage)
.then((/**
* @param {?} resizeResult
* @return {?}
*/
resizeResult => {
resizeResult.toBlob((/**
* @param {?} blob
* @return {?}
*/
(blob) => {
this.editResult.emit(blob);
this.processing.emit(false);
}), this.originalImage.type);
}));
}
else {
this.editedImage.toBlob((/**
* @param {?} blob
* @return {?}
*/
(blob) => {
this.editResult.emit(blob);
this.processing.emit(false);
}), this.originalImage.type);
}
});
}
/**
* open the bottom sheet for selecting filters, and applies the selected filter in preview mode
* @private
* @return {?}
*/
chooseFilters() {
/** @type {?} */
const data = { filter: this.selectedFilter };
/** @type {?} */
const bottomSheetRef = this.bottomSheet.open(NgxFilterMenuComponent, {
data: data
});
bottomSheetRef.afterDismissed().subscribe((/**
* @return {?}
*/
() => {
this.selectedFilter = data.filter;
this.applyFilter(true);
}));
}
// *************************** //
// File Input & Output Methods //
// *************************** //
/**
* load image from input field
* @private
* @param {?} file
* @return {?}
*/
loadFile(file) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => __awaiter(this, void 0, void 0, function* () {
this.processing.emit(true);
try {
yield this.readImage(file);
}
catch (err) {
console.error(err);
this.error.emit(new Error(err));
}
try {
yield this.showPreview();
}
catch (err) {
console.error(err);
this.error.emit(new Error(err));
}
// set pane limits
// show points
this.imageLoaded = true;
yield this.limitsService.setPaneDimensions({ width: this.previewDimensions.width, height: this.previewDimensions.height });
setTimeout((/**
* @return {?}
*/
() => __awaiter(this, void 0, void 0, function* () {
yield this.detectContours();
this.processing.emit(false);
resolve();
})), 15);
})));
}
/**
* read image from File object
* @private
* @param {?} file
* @return {?}
*/
readImage(file) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => __awaiter(this, void 0, void 0, function* () {
/** @type {?} */
let imageSrc;
try {
imageSrc = yield readFile();
}
catch (err) {
reject(err);
}
/** @type {?} */
const img = new Image();
img.onload = (/**
* @return {?}
*/
() => __awaiter(this, void 0, void 0, function* () {
// set edited image canvas and dimensions
this.editedImage = (/** @type {?} */ (document.createElement('canvas')));
this.editedImage.width = img.width;
this.editedImage.height = img.height;
/** @type {?} */
const ctx = this.editedImage.getContext('2d');
ctx.drawImage(img, 0, 0);
// resize image if larger than max image size
/** @type {?} */
const width = img.width > img.height ? img.height : img.width;
if (width > this.options.maxImageDimensions.width) {
this.editedImage = yield this.resize(this.editedImage);
}
this.imageDimensions.width = this.editedImage.width;
this.imageDimensions.height = this.editedImage.height;
this.setPreviewPaneDimensions(this.editedImage);
resolve();
}));
img.src = imageSrc;
})));
/**
* read file from input field
* @return {?}
*/
function readFile() {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
/** @type {?} */
const reader = new FileReader();
reader.onload = (/**
* @param {?} event
* @return {?}
*/
(event) => {
resolve(reader.result);
});
reader.onerror = (/**
* @param {?} err
* @return {?}
*/
(err) => {
reject(err);
});
reader.readAsDataURL(file);
}));
}
}
// ************************ //
// Image Processing Methods //
// ************************ //
/**
* rotate image 90 degrees
* @private
* @return {?}
*/
rotateImage() {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
this.processing.emit(true);
setTimeout((/**
* @return {?}
*/
() => {
/** @type {?} */
const 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
/** @type {?} */
const initialPreviewDimensions = { width: 0, height: 0 };
Object.assign(initialPreviewDimensions, this.previewDimensions);
/** @type {?} */
const initialPositions = Array.from(this.points);
// get new dimensions
// set new preview pane dimensions
this.setPreviewPaneDimensions(this.editedImage);
// get preview pane resize ratio
/** @type {?} */
const 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((/**
* @return {?}
*/
() => {
this.processing.emit(false);
resolve();
}));
}), 30);
}));
}
/**
* detects the contours of the document and
*
* @private
* @return {?}
*/
detectContours() {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
this.processing.emit(true);
setTimeout((/**
* @return {?}
*/
() => {
// load the image and compute the ratio of the old height to the new height, clone it, and resize it
/** @type {?} */
const processingResizeRatio = 0.5;
/** @type {?} */
const dst = cv.imread(this.editedImage);
/** @type {?} */
const dsize = new cv.Size(dst.rows * processingResizeRatio, dst.cols * processingResizeRatio);
/** @type {?} */
const 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);
/** @type {?} */
const contours = new cv.MatVector();
/** @type {?} */
const hierarchy = new cv.Mat();
cv.findContours(dst, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);
/** @type {?} */
const rect = cv.boundingRect(dst);
dst.delete();
hierarchy.delete();
contours.delete();
// transform the rectangle into a set of points
Object.keys(rect).forEach((/**
* @param {?} key
* @return {?}
*/
key => {
rect[key] = rect[key] * this.imageResizeRatio;
}));
/** @type {?} */
const 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
* @private
* @return {?}
*/
transform() {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
this.processing.emit(true);
setTimeout((/**
* @return {?}
*/
() => {
/** @type {?} */
const dst = cv.imread(this.editedImage);
// create source coordinates matrix
/** @type {?} */
const sourceCoordinates = [
this.getPoint(['top', 'left']),
this.getPoint(['top', 'right']),
this.getPoint(['bottom', 'right']),
this.getPoint(['bottom', 'left'])
].map((/**
* @param {?} point
* @return {?}
*/
point => {
return [point.x / this.imageResizeRatio, point.y / this.imageResizeRatio];
}));
// get max width
/** @type {?} */
const bottomWidth = this.getPoint(['bottom', 'right']).x - this.getPoint(['bottom', 'left']).x;
/** @type {?} */
const topWidth = this.getPoint(['top', 'right']).x - this.getPoint(['top', 'left']).x;
/** @type {?} */
const maxWidth = Math.max(bottomWidth, topWidth) / this.imageResizeRatio;
// get max height
/** @type {?} */
const leftHeight = this.getPoint(['bottom', 'left']).y - this.getPoint(['top', 'left']).y;
/** @type {?} */
const rightHeight = this.getPoint(['bottom', 'right']).y - this.getPoint(['top', 'right']).y;
/** @type {?} */
const maxHeight = Math.max(leftHeight, rightHeight) / this.imageResizeRatio;
// create dest coordinates matrix
/** @type {?} */
const destCoordinates = [
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]
];
// convert to open cv matrix objects
/** @type {?} */
const Ms = cv.matFromArray(4, 1, cv.CV_32FC2, [].concat(...sourceCoordinates));
/** @type {?} */
const Md = cv.matFromArray(4, 1, cv.CV_32FC2, [].concat(...destCoordinates));
/** @type {?} */
const transformMatrix = cv.getPerspectiveTransform(Ms, Md);
// set new image size
/** @type {?} */
const 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((/**
* @return {?}
*/
() => {
this.processing.emit(false);
resolve();
}));
}), 30);
}));
}
/**
* applies the selected filter to the image
* @private
* @param {?} preview - when true, will not apply the filter to the edited image but only display a preview.
* when false, will apply to editedImage
* @return {?}
*/
applyFilter(preview) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => __awaiter(this, void 0, void 0, function* () {
this.processing.emit(true);
// default options
/** @type {?} */
const options = {
blur: false,
th: true,
thMode: cv.ADAPTIVE_THRESH_MEAN_C,
thMeanCorrection: 10,
thBlockSize: 25,
thMax: 255,
grayScale: true,
};
/** @type {?} */
const dst = cv.imread(this.editedImage);
switch (this.selectedFilter) {
case 'default':
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((/**
* @return {?}
*/
() => __awaiter(this, void 0, void 0, function* () {
if (options.grayScale) {
cv.cvtColor(dst, dst, cv.COLOR_RGBA2GRAY, 0);
}
if (options.blur) {
/** @type {?} */
const 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);
}
yield this.showPreview(dst);
this.processing.emit(false);
resolve();
})), 30);
})));
}
/**
* resize an image to fit constraints set in options.maxImageDimensions
* @private
* @param {?} image
* @return {?}
*/
resize(image) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
this.processing.emit(true);
setTimeout((/**
* @return {?}
*/
() => {
/** @type {?} */
const src = cv.imread(image);
/** @type {?} */
const currentDimensions = {
width: src.size().width,
height: src.size().height
};
/** @type {?} */
const 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;
}
/** @type {?} */
const dsize = new cv.Size(Math.floor(resizeDimensions.width), Math.floor(resizeDimensions.height));
cv.resize(src, src, dsize, 0, 0, cv.INTER_AREA);
/** @type {?} */
const resizeResult = (/** @type {?} */ (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
* @private
* @param {?=} image
* @return {?}
*/
showPreview(image) {
return new Promise((/**
* @param {?} resolve
* @param {?} reject
* @return {?}
*/
(resolve, reject) => {
/** @type {?} */
let src;
if (image) {
src = image;
}
else {
src = cv.imread(this.editedImage);
}
/** @type {?} */
const dst = new cv.Mat();
/** @type {?} */
const 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
* @private
* @param {?} img
* @return {?}
*/
setPreviewPaneDimensions(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
* @private
* @param {?} width
* @param {?} height
* @return {?}
*/
calculateDimensions(width, height) {
/** @type {?} */
const ratio = width / height;
/** @type {?} */
const maxWidth = this.screenDimensions.width > this.maxPreviewWidth ?
this.maxPreviewWidth : this.screenDimensions.width - 40;
/** @type {?} */
const maxHeight = this.screenDimensions.height - 240;
/** @type {?} */
const 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
* @private
* @param {?} roles - an array of roles by which the point will be fetched
* @return {?}
*/
getPoint(roles) {
return this.points.find((/**
* @param {?} point
* @return {?}
*/
point => {
return this.limitsService.compareArray(point.roles, roles);
}));
}
}
NgxDocScannerComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-doc-scanner',
template: "<div [ngStyle]=\"editorStyle\" fxLayoutAlign=\"space-around\" style=\"direction: ltr !important\">\n <div #imageContainer [ngStyle]=\"imageDivStyle\" style=\"margin: auto;\" >\n <ng-container *ngIf=\"imageLoaded && mode === 'crop'\">\n <ngx-shape-outine #shapeOutline [color]=\"options.cropToolColor\" [weight]=\"options.cropToolLineWeight\" [dimensions]=\"previewDimensions\"></ngx-shape-outine>\n <ngx-draggable-point #topLeft [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: 0, y: 0}\" [limitRoles]=\"['top', 'left']\" [container]=\"imageContainer\"></ngx-draggable-point>\n <ngx-draggable-point #topRight [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: previewDimensions.width, y: 0}\" [limitRoles]=\"['top', 'right']\" [container]=\"imageContainer\"></ngx-draggable-point>\n <ngx-draggable-point #bottomLeft [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: 0, y: previewDimensions.height}\" [limitRoles]=\"['bottom', 'left']\" [container]=\"imageContainer\"></ngx-draggable-point>\n <ngx-draggable-point #bottomRight [pointOptions]=\"options.pointOptions\" [startPosition]=\"{x: previewDimensions.width, y: previewDimensions.height}\" [limitRoles]=\"['bottom', 'right']\" [container]=\"imageContainer\"></ngx-draggable-point>\n </ng-container>\n <canvas #PreviewCanvas [ngStyle]=\"{'max-width': options.maxPreviewWidth}\" style=\"z-index: 5\" ></canvas>\n </div>\n <div class=\"editor-actions\" fxLayout=\"row\" fxLayoutAlign=\"space-around\" style=\"position: absolute; bottom: 0; width: 100vw\">\n <ng-container *ngFor=\"let button of displayedButtons\" [ngSwitch]=\"button.type\">\n <button mat-mini-fab *ngSwitchCase=\"'fab'\" [name]=\"button.name\" (click)=\"button.action()\" [color]=\"options.buttonThemeColor\">\n <mat-icon>{{button.icon}}</mat-icon>\n </button>\n <button mat-raised-button *ngSwitchCase=\"'button'\" [name]=\"button.name\" (click)=\"button.action()\" [color]=\"options.buttonThemeColor\">\n <mat-icon>{{button.icon}}</mat-icon>\n <span>{{button.text}}}</span>\n </button>\n </ng-container>\n </div>\n</div>\n\n\n",
styles: [".editor-actions{padding:12px}.editor-actions button{margin:5px}"]
}] }
];
/** @nocollapse */
NgxDocScannerComponent.ctorParameters = () => [
{ type: NgxOpenCVService },
{ type: LimitsService },
{ type: MatBottomSheet }
];
NgxDocScannerComponent.propDecorators = {
previewCanvas: [{ type: ViewChild, args: ['PreviewCanvas', { read: ElementRef },] }],
exitEditor: [{ type: Output }],
editResult: [{ type: Output }],
error: [{ type: Output }],
ready: [{ type: Output }],
processing: [{ type: Output }],
file: [{ type: Input }],
config: [{ type: Input }]
};
if (false) {
/**
* editor config object
* @type {?}
*/
NgxDocScannerComponent.prototype.options;
/**
* an array of action buttons displayed on the editor screen
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.editorButtons;
/**
* max width of the preview area
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.maxPreviewWidth;
/**
* dimensions of the image container
* @type {?}
*/
NgxDocScannerComponent.prototype.imageDivStyle;
/**
* editor div style
* @type {?}
*/
NgxDocScannerComponent.prototype.editorStyle;
/**
* state of opencv loading
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.cvState;
/**
* true after the image is loaded and preview is displayed
* @type {?}
*/
NgxDocScannerComponent.prototype.imageLoaded;
/**
* editor mode
* @type {?}
*/
NgxDocScannerComponent.prototype.mode;
/**
* filter selected by the user, returned by the filter selector bottom sheet
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.selectedFilter;
/**
* viewport dimensions
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.screenDimensions;
/**
* image dimensions
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.imageDimensions;
/**
* dimensions of the preview pane
* @type {?}
*/
NgxDocScannerComponent.prototype.previewDimensions;
/**
* ration between preview image and original
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.imageResizeRatio;
/**
* stores the original image for reset purposes
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.originalImage;
/**
* stores the edited image
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.editedImage;
/**
* stores the preview image as canvas
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.previewCanvas;
/**
* an array of points used by the crop tool
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.points;
/**
* optional binding to the exit button of the editor
* @type {?}
*/
NgxDocScannerComponent.prototype.exitEditor;
/**
* fires on edit completion
* @type {?}
*/
NgxDocScannerComponent.prototype.editResult;
/**
* emits errors, can be linked to an error handler of choice
* @type {?}
*/
NgxDocScannerComponent.prototype.error;
/**
* emits the loading status of the cv module.
* @type {?}
*/
NgxDocScannerComponent.prototype.ready;
/**
* emits true when processing is done, false when completed
* @type {?}
*/
NgxDocScannerComponent.prototype.processing;
/**
* editor configuration object
* @type {?}
*/
NgxDocScannerComponent.prototype.config;
/**
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.ngxOpenCv;
/**
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.limitsService;
/**
* @type {?}
* @private
*/
NgxDocScannerComponent.prototype.bottomSheet;
}
/**
* a class for generating configuration objects for the editor
*/
class ImageEditorConfig {
/**
* @param {?} options
*/
constructor(options) {
/**
* 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((/**
* @param {?} key
* @return {?}
*/
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);
}
}
if (false) {
/**
* max dimensions of oputput image. if set to zero
* @type {?}
*/
ImageEditorConfig.prototype.maxImageDimensions;
/**
* background color of the main editor div
* @type {?}
*/
ImageEditorConfig.prototype.editorBackgroundColor;
/**
* css properties for the main editor div
* @type {?}
*/
ImageEditorConfig.prototype.editorDimensions;
/**
* css that will be added to the main div of the editor component
* @type {?}
*/
ImageEditorConfig.prototype.extraCss;
/**
* material design theme color name
* @type {?}
*/
ImageEditorConfig.prototype.buttonThemeColor;
/**
* icon for the button that completes the editing and emits the edited image
* @type {?}
*/
ImageEditorConfig.prototype.exportImageIcon;
/**
* color of the crop tool
* @type {?}
*/
ImageEditorConfig.prototype.cropToolColor;
/**
* shape of the crop tool, can be either a rectangle or a circle
* @type {?}
*/
ImageEditorConfig.prototype.cropToolShape;
/**
* dimensions of the crop tool
* @type {?}
*/
ImageEditorConfig.prototype.cropToolDimensions;
/**
* aggregation of the properties regarding point attributes generated by the class constructor
* @type {?}
*/
ImageEditorConfig.prototype.pointOptions;
/**
* aggregation of the properties regarding the editor style generated by the class constructor
* @type {?}
*/
ImageEditorConfig.prototype.editorStyle;
/**
* crop tool outline width
* @type {?}
*/
ImageEditorConfig.prototype.cropToolLineWeight;
/**
* maximum size of the preview pane
* @type {?}
*/
ImageEditorConfig.prototype.maxPreviewWidth;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/ngx-document-scanner.module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxDocumentScannerModule {
/**
* @param {?} config
* @return {?}
*/
static forRoot(config) {
return {
ngModule: NgxDocumentScannerModule,
providers: [
{ provide: OpenCvConfigToken, useValue: config },
],
};
}
}
NgxDocumentScannerModule.decorators = [
{ type: NgModule, args: [{
declarations: [
NgxDraggablePointComponent,
NgxFilterMenuComponent,
NgxShapeOutlineComponent,
NgxDocScannerComponent,
],
imports: [
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatBottomSheetModule,
MatListModule,
AngularDraggableModule,
CommonModule,
NgxOpenCVModule,
],
exports: [
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatBottomSheetModule,
MatListModule,
AngularDraggableModule,
NgxDocScannerComponent,
],
entryComponents: [
NgxFilterMenuComponent,
],
providers: [
NgxOpenCVService,
LimitsService,
]
},] }
];
/**
* @fileoverview added by tsickle
* Generated from: lib/PublicModels.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @record
*/
function OpenCVState() { }
if (false) {
/** @type {?} */
OpenCVState.prototype.ready;
/** @type {?} */
OpenCVState.prototype.loading;
/** @type {?} */
OpenCVState.prototype.error;
/** @type {?} */
OpenCVState.prototype.state;
}
/**
* describes an object with width and height properties
* @record
*/
function ImageDimensions() { }
if (false) {
/** @type {?} */
ImageDimensions.prototype.width;
/** @type {?} */
ImageDimensions.prototype.height;
}
/**
* describes a configuration object for the editor
* @record
*/
function DocScannerConfig() { }
if (false) {
/**
* max dimensions of output image. if set to zero will not resize the image
* @type {?|undefined}
*/
DocScannerConfig.prototype.maxImageDimensions;
/**
* background color of the main editor div
* @type {?|undefined}
*/
DocScannerConfig.prototype.editorBackgroundColor;
/**
* css properties for the main editor div
* @type {?|undefined}
*/
DocScannerConfig.prototype.editorDimensions;
/**
* css that will be added to the main div of the editor component
* @type {?|undefined}
*/
DocScannerConfig.prototype.extraCss;
/**
* material design theme color name
* @type {?|undefined}
*/
DocScannerConfig.prototype.buttonThemeColor;
/**
* icon for the button that completes the editing and emits the edited image
* @type {?|undefined}
*/
DocScannerConfig.prototype.exportImageIcon;
/**
* color of the crop tool (points and connecting lines)
* @type {?|undefined}
*/
DocScannerConfig.prototype.cropToolColor;
/**
* shape of the crop tool points
* @type {?|undefined}
*/
DocScannerConfig.prototype.cropToolShape;
/**
* width and height of the crop tool points
* @type {?|undefined}
*/
DocScannerConfig.prototype.cropToolDimensions;
/**
* weight of the crop tool's connecting lines
* @type {?|undefined}
*/
DocScannerConfig.prototype.cropToolLineWeight;
/**
* max width of the preview pane
* @type {?|undefined}
*/
DocScannerConfig.prototype.maxPreviewWidth;
}
/**
* describes a configuration object for the OpenCV service
* @record
*/
function OpenCVConfig() { }
if (false) {
/**
* path to the directory containing the OpenCV files, in the form of '/path/to/<opencv directory>'
* directory must contain the the following files:
* --<OpenCvDir>
* ----opencv.js
* ----opencv_js.wasm
* @type {?|undefined}
*/
OpenCVConfig.prototype.openCVDirPath;
/**
* additional callback that will run when OpenCv has finished loading and parsing
* @type {?|undefined}
*/
OpenCVConfig.prototype.runOnOpenCVInit;
}
/**
* @fileoverview added by tsickle
* Generated from: public_api.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* Generated from: autonomdev-ngx-document-scanner.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { NgxDocScannerComponent, NgxDocumentScannerModule, NgxDraggablePointComponent as ɵa, LimitsService as ɵb, NgxFilterMenuComponent as ɵc, NgxShapeOutlineComponent as ɵd };
//# sourceMappingURL=autonomdev-ngx-document-scanner.js.map