├── .DS_Store ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── editor.png ├── dist.tgz ├── dist ├── LICENSE ├── README.md ├── bundles │ ├── ngx-image-editor.umd.js │ └── ngx-image-editor.umd.min.js ├── esm2015 │ └── ngx-image-editor.js ├── esm5 │ └── ngx-image-editor.js ├── ngx-image-editor.component.d.ts ├── ngx-image-editor.d.ts ├── ngx-image-editor.metadata.json ├── ngx-image-editor.module.d.ts ├── package.json └── public_api.d.ts ├── ng-package.json ├── package-lock.json ├── package.json ├── src ├── .DS_Store ├── ngx-image-editor.component.d.ts ├── ngx-image-editor.component.ts ├── ngx-image-editor.module.d.ts ├── ngx-image-editor.module.ts ├── public_api.d.ts ├── public_api.js └── public_api.ts ├── tsconfig.json └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hggeorgiev/ngx-image-editor/b9b49316ec0f621d356b3bba5ac164487fe4578d/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node generated files 2 | npm-debug.log 3 | node_modules 4 | jspm_packages 5 | .idea 6 | lib 7 | build 8 | demo/node_modules 9 | 10 | # Remove tsc generated js and map file 11 | *.map 12 | src/ngx-image-editor.component.js 13 | src/ngx-image-editor.component.js.map 14 | src/ngx-image-editor.module.js 15 | src/ngx-image-editor.module.js.map -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node generated files 2 | node_modules 3 | npm-debug.log 4 | # OS generated files 5 | Thumbs.db 6 | .DS_Store 7 | node_modules 8 | src 9 | demo 10 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - .ng_pkg_build 6 | notifications: 7 | email: false 8 | node_js: 9 | - '10' 10 | 11 | install: 12 | - yarn install 13 | after_success: 14 | - npm run semantic-release 15 | branches: 16 | only: 17 | - master -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [1.3.1] - 2017-03-24 5 | ### Changes 6 | - Upgraded Angular, Angular Material, Angular Flex Layout. 7 | - Changed all material attributes and tags from md to mat. 8 | - Now the editor is a regular component not a Material Dialog component. 9 | 10 | 11 | ## [1.4.0] - 2017-05-11 12 | ### Changes 13 | - Upgraded Angular, Angular Material, Angular Flex Layout. 14 | - Removed all color-related styles 15 | - Removed modal functionality (we shouldn't assume ttrthat users may use it in a modal) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Centroida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

6 | 7 |

ngx-image-editor

8 |

9 | 10 | 11 | Awesome editor for Angular 6 based on [Angular Material](https://github.com/angular/material2) 12 | 13 | [![npm version](https://badge.fury.io/js/ngx-image-editor.svg)](https://badge.fury.io/js/ngx-image-editor) 14 | [![Build Status](https://travis-ci.org/Centroida/ngx-image-editor.svg?branch=master)](https://travis-ci.org/hggeorgiev/ngx-image-editor) 15 | 16 | **[Live Demo on Slackblitz](https://stackblitz.com/edit/ngx-image-editor-demo)** 17 | 18 | ## Getting started 19 | 20 | ##### Step 1: Install Angular Material (+ Material Icons) and Angular Flex Layout 21 | 22 | - [Angular Material](https://material.angular.io/guide/getting-started) 23 | - [Angular Flex-Layout](https://github.com/angular/flex-layout) 24 | 25 | ##### Step 2: Install cropperjs 26 | 27 | ```bash 28 | yarn add cropperjs 29 | ``` 30 | ##### Step 3: Add `cropperjs` file paths in your `.angular.json` 31 | 32 | ```json 33 | } 34 | "styles": [ 35 | "node_modules/cropperjs/dist/cropper.css" 36 | ], 37 | "scripts": [ 38 | "node_modules/cropperjs/dist/cropper.js" 39 | ] 40 | } 41 | ``` 42 | 43 | 44 | ##### Step 4: Install `ngx-image-editor`: 45 | ```bash 46 | yarn add ngx-image-editor 47 | ``` 48 | 49 | ##### Step 5: Import the `NgxImageEditorModule` within your app: 50 | ```js 51 | import {NgxImageEditorModule} from "ngx-image-editor"; 52 | 53 | @NgModule({ 54 | imports: [ 55 | NgxImageEditorModule 56 | ] 57 | }) 58 | ``` 59 | 60 | 61 | ##### Step 6: Use within your application: 62 | ```html 63 | 64 | ``` 65 | 66 | ### API 67 | 68 | | Property | Description | 69 | | -------------- | -------------------------------------------------------------- | 70 | | `[config]` | An object containing editor configuration (see **Configuration**) | 71 | | `(file)` | The emitted file after editing. | 72 | 73 | 74 | #### Configuration 75 | | Property | Description | 76 | | -------------- | -------------------------------------------------------------- | 77 | | ImageName | Name of the image. | 78 | | ImageUrl | URL of the image (if it coming from a CDN) . | 79 | | File | File object of the image (if it is being uploaded through the browser. | 80 | | ImageType | Type of the image (default is `image/jpeg`). | 81 | | AspectRatios | Array of aspect ratios. Available options: `0:0`, `1:1` , `2:3` ,`4:3`, `16:9`l . (default is `0:0`) | 82 | 83 | 84 | ### Example 85 | 86 | ```typescript 87 | 88 | @Component({ 89 | selector: 'app-root', 90 | styleUrls: ['./app.component.css'] 91 | template: ` 92 | 96 | 97 | 98 | `, 99 | 100 | }) 101 | export class AppComponent { 102 | public config = { 103 | ImageName: 'Some image', 104 | AspectRatios: ["4:3", "16:9"], 105 | ImageUrl: 'https://static.pexels.com/photos/248797/pexels-photo-248797.jpeg', 106 | ImageType: 'image/jpeg' 107 | } 108 | 109 | public close() { 110 | // Fired when the editor is closed. 111 | } 112 | 113 | public getEditedFile(file: File) { 114 | // Fired when the file has been processed. 115 | } 116 | } 117 | 118 | 119 | ``` 120 | 121 | 122 | ### Contributors 123 | 124 | | [![Hristo Georgiev](https://github.com/hggeorgiev.png?size=100)](https://github.com/hggeorgiev) | [![Taulant Disha](https://github.com/taulantdisha.png?size=100)](https://github.com/taulantdisha) | 125 | |---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| 126 | | [Hristo Georgiev](https://github.com/hggeorgiev) | [Taulant Disha](https://github.com/taulantdisha) | 127 | 128 | 129 | -------------------------------------------------------------------------------- /assets/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hggeorgiev/ngx-image-editor/b9b49316ec0f621d356b3bba5ac164487fe4578d/assets/editor.png -------------------------------------------------------------------------------- /dist.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hggeorgiev/ngx-image-editor/b9b49316ec0f621d356b3bba5ac164487fe4578d/dist.tgz -------------------------------------------------------------------------------- /dist/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Centroida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

6 | 7 |

ngx-image-editor

8 |

9 | 10 | 11 | Awesome editor for Angular 6 based on [Angular Material](https://github.com/angular/material2) 12 | 13 | [![npm version](https://badge.fury.io/js/ngx-image-editor.svg)](https://badge.fury.io/js/ngx-image-editor) 14 | [![Build Status](https://travis-ci.org/Centroida/ngx-image-editor.svg?branch=master)](https://travis-ci.org/hggeorgiev/ngx-image-editor) 15 | 16 | **[Live Demo on Slackblitz](https://stackblitz.com/edit/ngx-image-editor-demo)** 17 | 18 | ## Getting started 19 | 20 | ##### Step 1: Install Angular Material (+ Material Icons) and Angular Flex Layout 21 | 22 | - [Angular Material](https://material.angular.io/guide/getting-started) 23 | - [Angular Flex-Layout](https://github.com/angular/flex-layout) 24 | 25 | ##### Step 2: Install cropperjs 26 | 27 | ```bash 28 | npm install --save cropperjs 29 | ``` 30 | ##### Step 3: Add `cropperjs` file paths in your `.angular.json` 31 | 32 | ```json 33 | } 34 | "styles": [ 35 | "node_modules/cropperjs/dist/cropper.css" 36 | ], 37 | "scripts": [ 38 | "node_modules/cropperjs/dist/cropper.js" 39 | ] 40 | } 41 | ``` 42 | 43 | 44 | ##### Step 4: Install `ngx-image-editor`: 45 | ```bash 46 | npm install --save ngx-image-editor 47 | ``` 48 | 49 | ##### Step 5: Import the `NgxImageEditorModule` within your app: 50 | ```js 51 | import {NgxImageEditorModule} from "ngx-image-editor"; 52 | 53 | @NgModule({ 54 | imports: [ 55 | NgxImageEditorModule 56 | ] 57 | }) 58 | ``` 59 | 60 | 61 | ##### Step 6: Use within your application: 62 | ```html 63 | 64 | ``` 65 | 66 | ### API 67 | 68 | | Property | Description | 69 | | -------------- | -------------------------------------------------------------- | 70 | | `[config]` | An object containing editor configuration (see **Configuration**) | 71 | | `(file)` | The emitted file after editing. | 72 | 73 | 74 | #### Configuration 75 | | Property | Description | 76 | | -------------- | -------------------------------------------------------------- | 77 | | ImageName | Name of the image. | 78 | | ImageUrl | URL of the image (if it coming from a CDN) . | 79 | | File | File object of the image (if it is being uploaded through the browser. | 80 | | ImageType | Type of the image (default is `image/jpeg`). | 81 | | AspectRatios | Array of aspect ratios. Available options: `0:0`, `1:1` , `2:3` ,`4:3`, `16:9`l . (default is `0:0`) | 82 | 83 | 84 | ### Example 85 | 86 | ```typescript 87 | 88 | @Component({ 89 | selector: 'app-root', 90 | styleUrls: ['./app.component.css'] 91 | template: ` 92 | 96 | 97 | 98 | `, 99 | 100 | }) 101 | export class AppComponent { 102 | public config = { 103 | ImageName: 'Some image', 104 | AspectRatios: ["4:3", "16:9"], 105 | ImageUrl: 'https://static.pexels.com/photos/248797/pexels-photo-248797.jpeg', 106 | ImageType: 'image/jpeg' 107 | } 108 | 109 | public close() { 110 | // Fired when the editor is closed. 111 | } 112 | 113 | public getEditedFile(file: File) { 114 | // Fired when the file has been processed. 115 | } 116 | } 117 | 118 | 119 | ``` 120 | 121 | 122 | ### Contributors 123 | 124 | | [![Hristo Georgiev](https://github.com/hggeorgiev.png?size=100)](https://github.com/hggeorgiev) | [![Taulant Disha](https://github.com/taulantdisha.png?size=100)](https://github.com/taulantdisha) | 125 | |---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| 126 | | [Hristo Georgiev](https://github.com/hggeorgiev) | [Taulant Disha](https://github.com/taulantdisha) | 127 | 128 | 129 | -------------------------------------------------------------------------------- /dist/bundles/ngx-image-editor.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/flex-layout'), require('@angular/material'), require('@angular/forms'), require('@angular/common'), require('@angular/platform-browser/animations')) : 3 | typeof define === 'function' && define.amd ? define('ngx-image-editor', ['exports', '@angular/core', '@angular/flex-layout', '@angular/material', '@angular/forms', '@angular/common', '@angular/platform-browser/animations'], factory) : 4 | (factory((global['ngx-image-editor'] = {}),global.ng.core,global.ng['flex-layout'],global.ng.material,global.ng.forms,global.ng.common,global.ng.platformBrowser.animations)); 5 | }(this, (function (exports,core,flexLayout,material,forms,common,animations) { 'use strict'; 6 | 7 | var NgxImageEditorComponent = /** @class */ (function () { 8 | function NgxImageEditorComponent() { 9 | this.file = new core.EventEmitter(); 10 | this.zoomIn = 0; 11 | this.sliderValue = 0; 12 | this.loading = true; 13 | this.canvasFillColor = '#fff'; 14 | this.state = new EditorOptions(); 15 | } 16 | Object.defineProperty(NgxImageEditorComponent.prototype, "config", { 17 | set: function (config) { 18 | this.state = config; 19 | }, 20 | enumerable: true, 21 | configurable: true 22 | }); 23 | NgxImageEditorComponent.prototype.ngOnInit = function () { 24 | this.handleStateConfig(); 25 | }; 26 | NgxImageEditorComponent.prototype.ngOnDestroy = function () { 27 | this.cropper.destroy(); 28 | }; 29 | NgxImageEditorComponent.prototype.ngAfterViewInit = function () { 30 | if (!this.state.File && this.state.ImageUrl) { 31 | this.initializeCropper(); 32 | } 33 | }; 34 | NgxImageEditorComponent.prototype.handleStateConfig = function () { 35 | this.state.ImageType = this.state.ImageType ? this.state.ImageType : 'image/jpeg'; 36 | if (this.state.ImageUrl) { 37 | this.state.File = null; 38 | this.previewImageURL = this.state.ImageUrl; 39 | } 40 | if (this.state.File) { 41 | this.state.ImageUrl = null; 42 | this.convertFileToBase64(this.state.File); 43 | } 44 | if (this.state.AspectRatios) { 45 | this.addRatios(this.state.AspectRatios); 46 | } 47 | else { 48 | this.ratios = NGX_DEFAULT_RATIOS; 49 | } 50 | if (!this.state.ImageUrl && !this.state.File) { 51 | console.error("Property ImageUrl or File is missing, Please provide an url or file in the config options."); 52 | } 53 | if (!this.state.ImageName) { 54 | console.error("Property ImageName is missing, Please provide a name for the image."); 55 | } 56 | }; 57 | NgxImageEditorComponent.prototype.convertFileToBase64 = function (file) { 58 | var _this = this; 59 | var reader = new FileReader(); 60 | reader.addEventListener("load", function (e) { 61 | _this.previewImageURL = e.target["result"]; 62 | }, false); 63 | reader.readAsDataURL(file); 64 | reader.onloadend = (function () { 65 | _this.initializeCropper(); 66 | }); 67 | }; 68 | NgxImageEditorComponent.prototype.addRatios = function (ratios) { 69 | var _this = this; 70 | this.ratios = []; 71 | ratios.forEach(function (ratioType) { 72 | var addedRation = NGX_DEFAULT_RATIOS.find(function (ratio) { 73 | return ratio.text === ratioType; 74 | }); 75 | _this.ratios.push(addedRation); 76 | }); 77 | }; 78 | NgxImageEditorComponent.prototype.handleCrop = function () { 79 | var _this = this; 80 | this.loading = true; 81 | setTimeout(function () { 82 | _this.croppedImage = _this.cropper.getCroppedCanvas({ fillColor: _this.canvasFillColor }) 83 | .toDataURL(_this.state.ImageType); 84 | setTimeout(function () { 85 | _this.imageWidth = _this.croppedImg.nativeElement.width; 86 | _this.imageHeight = _this.croppedImg.nativeElement.height; 87 | }); 88 | _this.cropper.getCroppedCanvas({ fillColor: _this.canvasFillColor }).toBlob(function (blob) { 89 | _this.blob = blob; 90 | }); 91 | _this.zoomIn = 1; 92 | _this.loading = false; 93 | }, 2000); 94 | }; 95 | NgxImageEditorComponent.prototype.undoCrop = function () { 96 | var _this = this; 97 | this.croppedImage = null; 98 | this.blob = null; 99 | setTimeout(function () { 100 | _this.initializeCropper(); 101 | }, 100); 102 | }; 103 | NgxImageEditorComponent.prototype.saveImage = function () { 104 | this.file.emit(new File([this.blob], this.state.ImageName, { type: this.state.ImageType })); 105 | }; 106 | NgxImageEditorComponent.prototype.initializeCropper = function () { 107 | var _this = this; 108 | this.cropper = new Cropper(this.previewImage.nativeElement, { 109 | zoomOnWheel: true, 110 | viewMode: 0, 111 | center: true, 112 | ready: function () { return _this.loading = false; }, 113 | dragMode: 'move', 114 | crop: function (e) { 115 | _this.imageHeight = Math.round(e.detail.height); 116 | _this.imageWidth = Math.round(e.detail.width); 117 | _this.cropBoxWidth = Math.round(_this.cropper.getCropBoxData().width); 118 | _this.cropBoxHeight = Math.round(_this.cropper.getCropBoxData().height); 119 | _this.canvasWidth = Math.round(_this.cropper.getCanvasData().width); 120 | _this.canvasHeight = Math.round(_this.cropper.getCanvasData().height); 121 | } 122 | }); 123 | this.setRatio(this.ratios[0].value); 124 | }; 125 | NgxImageEditorComponent.prototype.setRatio = function (value) { 126 | this.cropper.setAspectRatio(value); 127 | }; 128 | NgxImageEditorComponent.prototype.zoomChange = function (input, zoom) { 129 | if (this.croppedImage) { 130 | if (zoom) { 131 | zoom === 'zoomIn' ? this.zoomIn += 0.1 : this.zoomIn -= 0.1; 132 | } 133 | else { 134 | if (input < this.sliderValue) { 135 | this.zoomIn = -Math.abs(input / 100); 136 | } 137 | else { 138 | this.zoomIn = Math.abs(input / 100); 139 | } 140 | } 141 | if (this.zoomIn <= 0.1) { 142 | this.zoomIn = 0.1; 143 | } 144 | } 145 | else { 146 | if (zoom) { 147 | this.cropper.zoom(input); 148 | this.zoomIn = input; 149 | } 150 | else { 151 | if (input < this.sliderValue) { 152 | this.cropper.zoom(-Math.abs(input / 100)); 153 | } 154 | else { 155 | this.cropper.zoom(Math.abs(input / 100)); 156 | } 157 | if (input === 0) { 158 | this.cropper.zoom(-1); 159 | } 160 | } 161 | } 162 | if (!zoom) { 163 | this.sliderValue = input; 164 | } 165 | else { 166 | input > 0 ? this.sliderValue += Math.abs(input * 100) : this.sliderValue -= Math.abs(input * 100); 167 | } 168 | if (this.sliderValue < 0) { 169 | this.sliderValue = 0; 170 | } 171 | }; 172 | NgxImageEditorComponent.prototype.setImageWidth = function (canvasWidth) { 173 | if (canvasWidth) { 174 | this.cropper.setCanvasData({ 175 | left: this.cropper.getCanvasData().left, 176 | top: this.cropper.getCanvasData().top, 177 | width: Math.round(canvasWidth), 178 | height: this.cropper.getCanvasData().height 179 | }); 180 | } 181 | }; 182 | NgxImageEditorComponent.prototype.setImageHeight = function (canvasHeight) { 183 | if (canvasHeight) { 184 | this.cropper.setCanvasData({ 185 | left: this.cropper.getCanvasData().left, 186 | top: this.cropper.getCanvasData().top, 187 | width: this.cropper.getCanvasData().width, 188 | height: Math.round(canvasHeight) 189 | }); 190 | } 191 | }; 192 | NgxImageEditorComponent.prototype.setCropBoxWidth = function (cropBoxWidth) { 193 | if (cropBoxWidth) { 194 | this.cropper.setCropBoxData({ 195 | left: this.cropper.getCropBoxData().left, 196 | top: this.cropper.getCropBoxData().top, 197 | width: Math.round(cropBoxWidth), 198 | height: this.cropper.getCropBoxData().height 199 | }); 200 | } 201 | }; 202 | NgxImageEditorComponent.prototype.setCropBoxHeight = function (cropBoxHeight) { 203 | if (cropBoxHeight) { 204 | this.cropper.setCropBoxData({ 205 | left: this.cropper.getCropBoxData().left, 206 | top: this.cropper.getCropBoxData().top, 207 | width: this.cropper.getCropBoxData().width, 208 | height: Math.round(cropBoxHeight) 209 | }); 210 | } 211 | }; 212 | NgxImageEditorComponent.prototype.centerCanvas = function () { 213 | var cropBoxLeft = (this.cropper.getContainerData().width - this.cropper.getCropBoxData().width) / 2; 214 | var cropBoxTop = (this.cropper.getContainerData().height - this.cropper.getCropBoxData().height) / 2; 215 | var canvasLeft = (this.cropper.getContainerData().width - this.cropper.getCanvasData().width) / 2; 216 | var canvasTop = (this.cropper.getContainerData().height - this.cropper.getCanvasData().height) / 2; 217 | this.cropper.setCropBoxData({ 218 | left: cropBoxLeft, 219 | top: cropBoxTop, 220 | width: this.cropper.getCropBoxData().width, 221 | height: this.cropper.getCropBoxData().height 222 | }); 223 | this.cropper.setCanvasData({ 224 | left: canvasLeft, 225 | top: canvasTop, 226 | width: this.cropper.getCanvasData().width, 227 | height: this.cropper.getCanvasData().height 228 | }); 229 | }; 230 | return NgxImageEditorComponent; 231 | }()); 232 | NgxImageEditorComponent.decorators = [ 233 | { type: core.Component, args: [{ 234 | selector: 'ngx-image-editor', 235 | template: "\n
\n
\n photo\n
{{state.ImageName}}
\n \n \n
\n\n
\n \n \n \n
\n \n \n
\n \n
\n
\n \n
\n\n \n\n
\n
Width: {{imageWidth}}px Height: {{imageHeight}}px
\n \n
\n \n \n \n
\n
\n
\n \n \n
\n
\n \n \n open_with\n \n \n crop\n \n \n\n \n \n {{ratio.text}}\n \n \n\n
\n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n\n \n \n\n \n\n ", 236 | styles: ["\n\n .ngx-image-editor-component .photo-editor-header {\n display: flex;\n justify-content: space-around;\n align-items: center;\n width: 100%;\n padding: 5px 0;\n z-index: 100;\n margin: 0;\n }\n\n .ngx-image-editor-component .photo-editor-header > .mat-icon {\n padding: 0 10px;\n }\n\n .ngx-image-editor-component .photo-editor-header > .file-name {\n flex: 1 1 100%;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n\n .ngx-image-editor-component mat-progress-spinner {\n position: absolute;\n }\n\n .ngx-image-editor-component .dialog-crop-container {\n width: 800px;\n height: 400px;\n overflow: hidden;\n }\n\n .ngx-image-editor-component .cropper-bg {\n background-image: none !important;\n }\n\n .ngx-image-editor-component .cropper-bg > .cropper-modal {\n opacity: 1 !important;\n background: none;\n }\n\n .ngx-image-editor-component .img-container {\n width: 800px !important;\n height: 400px !important;\n }\n\n .ngx-image-editor-component .cropped-image img {\n width: auto !important;\n height: auto !important;\n max-width: 800px !important;\n max-height: 400px !important;\n }\n\n .ngx-image-editor-component .dialog-button-actions {\n position: relative;\n padding: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions:last-child {\n margin: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group {\n margin: 20px;\n }\n\n .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config {\n padding: 5px;\n margin: 0 20px;\n }\n\n \n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker {\n width: 200px !important;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom {\n display: flex;\n align-items: center;\n padding: 0 10px;\n }\n \n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container {\n cursor: grab;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions {\n padding: 0 10px;\n font-size: 14px;\n width: 200px;\n max-width: 200px;\n }\n\n \n\n\n\n\n\n\n\n\n\n\n "], 237 | encapsulation: core.ViewEncapsulation.None 238 | },] }, 239 | ]; 240 | NgxImageEditorComponent.ctorParameters = function () { return []; }; 241 | NgxImageEditorComponent.propDecorators = { 242 | "previewImage": [{ type: core.ViewChild, args: ['previewimg',] },], 243 | "croppedImg": [{ type: core.ViewChild, args: ['croppedImg',] },], 244 | "config": [{ type: core.Input },], 245 | "file": [{ type: core.Output },], 246 | }; 247 | var EditorOptions = /** @class */ (function () { 248 | function EditorOptions() { 249 | } 250 | return EditorOptions; 251 | }()); 252 | var NGX_DEFAULT_RATIOS = [ 253 | { 254 | value: 16 / 9, text: '16:9' 255 | }, 256 | { 257 | value: 4 / 3, text: '4:3' 258 | }, 259 | { 260 | value: 1 / 1, text: '1:1' 261 | }, 262 | { 263 | value: 2 / 3, text: '2:3' 264 | }, 265 | { 266 | value: 0 / 0, text: 'Default' 267 | } 268 | ]; 269 | var NgxImageEditorModule = /** @class */ (function () { 270 | function NgxImageEditorModule() { 271 | } 272 | NgxImageEditorModule.forRoot = function () { 273 | return { 274 | ngModule: NgxImageEditorModule, 275 | }; 276 | }; 277 | return NgxImageEditorModule; 278 | }()); 279 | NgxImageEditorModule.decorators = [ 280 | { type: core.NgModule, args: [{ 281 | imports: [ 282 | forms.FormsModule, 283 | animations.BrowserAnimationsModule, 284 | common.CommonModule, 285 | forms.ReactiveFormsModule, 286 | flexLayout.FlexLayoutModule, 287 | material.MatButtonModule, 288 | material.MatIconModule, 289 | material.MatDialogModule, 290 | material.MatInputModule, 291 | material.MatMenuModule, 292 | material.MatProgressSpinnerModule, 293 | material.MatTabsModule, 294 | material.MatTooltipModule, 295 | material.MatButtonToggleModule, 296 | material.MatSliderModule, 297 | material.MatAutocompleteModule 298 | ], 299 | declarations: [ 300 | NgxImageEditorComponent 301 | ], 302 | exports: [NgxImageEditorComponent] 303 | },] }, 304 | ]; 305 | 306 | exports.NgxImageEditorModule = NgxImageEditorModule; 307 | exports.NgxImageEditorComponent = NgxImageEditorComponent; 308 | exports.EditorOptions = EditorOptions; 309 | exports.NGX_DEFAULT_RATIOS = NGX_DEFAULT_RATIOS; 310 | 311 | Object.defineProperty(exports, '__esModule', { value: true }); 312 | 313 | }))); 314 | //# sourceMappingURL=ngx-image-editor.umd.js.map 315 | -------------------------------------------------------------------------------- /dist/bundles/ngx-image-editor.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@angular/core"),require("@angular/flex-layout"),require("@angular/material"),require("@angular/forms"),require("@angular/common"),require("@angular/platform-browser/animations")):"function"==typeof define&&define.amd?define("ngx-image-editor",["exports","@angular/core","@angular/flex-layout","@angular/material","@angular/forms","@angular/common","@angular/platform-browser/animations"],e):e(t["ngx-image-editor"]={},t.ng.core,t.ng["flex-layout"],t.ng.material,t.ng.forms,t.ng.common,t.ng.platformBrowser.animations)}(this,function(t,e,n,o,a,i,r){"use strict";var p=function(){function t(){this.file=new e.EventEmitter,this.zoomIn=0,this.sliderValue=0,this.loading=!0,this.canvasFillColor="#fff",this.state=new s}return Object.defineProperty(t.prototype,"config",{set:function(t){this.state=t},enumerable:!0,configurable:!0}),t.prototype.ngOnInit=function(){this.handleStateConfig()},t.prototype.ngOnDestroy=function(){this.cropper.destroy()},t.prototype.ngAfterViewInit=function(){!this.state.File&&this.state.ImageUrl&&this.initializeCropper()},t.prototype.handleStateConfig=function(){this.state.ImageType=this.state.ImageType?this.state.ImageType:"image/jpeg",this.state.ImageUrl&&(this.state.File=null,this.previewImageURL=this.state.ImageUrl),this.state.File&&(this.state.ImageUrl=null,this.convertFileToBase64(this.state.File)),this.state.AspectRatios?this.addRatios(this.state.AspectRatios):this.ratios=l,this.state.ImageUrl||this.state.File||console.error("Property ImageUrl or File is missing, Please provide an url or file in the config options."),this.state.ImageName||console.error("Property ImageName is missing, Please provide a name for the image.")},t.prototype.convertFileToBase64=function(t){var e=this,n=new FileReader;n.addEventListener("load",function(t){e.previewImageURL=t.target.result},!1),n.readAsDataURL(t),n.onloadend=function(){e.initializeCropper()}},t.prototype.addRatios=function(t){var n=this;this.ratios=[],t.forEach(function(e){var t=l.find(function(t){return t.text===e});n.ratios.push(t)})},t.prototype.handleCrop=function(){var e=this;this.loading=!0,setTimeout(function(){e.croppedImage=e.cropper.getCroppedCanvas({fillColor:e.canvasFillColor}).toDataURL(e.state.ImageType),setTimeout(function(){e.imageWidth=e.croppedImg.nativeElement.width,e.imageHeight=e.croppedImg.nativeElement.height}),e.cropper.getCroppedCanvas({fillColor:e.canvasFillColor}).toBlob(function(t){e.blob=t}),e.zoomIn=1,e.loading=!1},2e3)},t.prototype.undoCrop=function(){var t=this;this.croppedImage=null,this.blob=null,setTimeout(function(){t.initializeCropper()},100)},t.prototype.saveImage=function(){this.file.emit(new File([this.blob],this.state.ImageName,{type:this.state.ImageType}))},t.prototype.initializeCropper=function(){var e=this;this.cropper=new Cropper(this.previewImage.nativeElement,{zoomOnWheel:!0,viewMode:0,center:!0,ready:function(){return e.loading=!1},dragMode:"move",crop:function(t){e.imageHeight=Math.round(t.detail.height),e.imageWidth=Math.round(t.detail.width),e.cropBoxWidth=Math.round(e.cropper.getCropBoxData().width),e.cropBoxHeight=Math.round(e.cropper.getCropBoxData().height),e.canvasWidth=Math.round(e.cropper.getCanvasData().width),e.canvasHeight=Math.round(e.cropper.getCanvasData().height)}}),this.setRatio(this.ratios[0].value)},t.prototype.setRatio=function(t){this.cropper.setAspectRatio(t)},t.prototype.zoomChange=function(t,e){this.croppedImage?(e?"zoomIn"===e?this.zoomIn+=.1:this.zoomIn-=.1:t\n
\n photo\n
{{state.ImageName}}
\n \n \n
\n\n
\n \n \n \n
\n \n \n
\n \n
\n
\n \n \n\n \n\n
\n
Width: {{imageWidth}}px Height: {{imageHeight}}px
\n \n
\n \n \n \n
\n
\n
\n \n \n
\n
\n \n \n open_with\n \n \n crop\n \n \n\n \n \n {{ratio.text}}\n \n \n\n
\n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \x3c!----\x3e\n\n \n \n\n \n\n ',styles:["\n\n .ngx-image-editor-component .photo-editor-header {\n display: flex;\n justify-content: space-around;\n align-items: center;\n width: 100%;\n padding: 5px 0;\n z-index: 100;\n margin: 0;\n }\n\n .ngx-image-editor-component .photo-editor-header > .mat-icon {\n padding: 0 10px;\n }\n\n .ngx-image-editor-component .photo-editor-header > .file-name {\n flex: 1 1 100%;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n\n .ngx-image-editor-component mat-progress-spinner {\n position: absolute;\n }\n\n .ngx-image-editor-component .dialog-crop-container {\n width: 800px;\n height: 400px;\n overflow: hidden;\n }\n\n .ngx-image-editor-component .cropper-bg {\n background-image: none !important;\n }\n\n .ngx-image-editor-component .cropper-bg > .cropper-modal {\n opacity: 1 !important;\n background: none;\n }\n\n .ngx-image-editor-component .img-container {\n width: 800px !important;\n height: 400px !important;\n }\n\n .ngx-image-editor-component .cropped-image img {\n width: auto !important;\n height: auto !important;\n max-width: 800px !important;\n max-height: 400px !important;\n }\n\n .ngx-image-editor-component .dialog-button-actions {\n position: relative;\n padding: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions:last-child {\n margin: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group {\n margin: 20px;\n }\n\n .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config {\n padding: 5px;\n margin: 0 20px;\n }\n\n \n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker {\n width: 200px !important;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom {\n display: flex;\n align-items: center;\n padding: 0 10px;\n }\n \n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container {\n cursor: grab;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions {\n padding: 0 10px;\n font-size: 14px;\n width: 200px;\n max-width: 200px;\n }\n\n \n\n\n\n\n\n\n\n\n\n\n "],encapsulation:e.ViewEncapsulation.None}]}],p.ctorParameters=function(){return[]},p.propDecorators={previewImage:[{type:e.ViewChild,args:["previewimg"]}],croppedImg:[{type:e.ViewChild,args:["croppedImg"]}],config:[{type:e.Input}],file:[{type:e.Output}]};var s=function(){},l=[{value:16/9,text:"16:9"},{value:4/3,text:"4:3"},{value:1,text:"1:1"},{value:2/3,text:"2:3"},{value:NaN,text:"Default"}],g=function(){function t(){}return t.forRoot=function(){return{ngModule:t}},t}();g.decorators=[{type:e.NgModule,args:[{imports:[a.FormsModule,r.BrowserAnimationsModule,i.CommonModule,a.ReactiveFormsModule,n.FlexLayoutModule,o.MatButtonModule,o.MatIconModule,o.MatDialogModule,o.MatInputModule,o.MatMenuModule,o.MatProgressSpinnerModule,o.MatTabsModule,o.MatTooltipModule,o.MatButtonToggleModule,o.MatSliderModule,o.MatAutocompleteModule],declarations:[p],exports:[p]}]}],t.NgxImageEditorModule=g,t.NgxImageEditorComponent=p,t.EditorOptions=s,t.NGX_DEFAULT_RATIOS=l,Object.defineProperty(t,"__esModule",{value:!0})}); 2 | //# sourceMappingURL=ngx-image-editor.umd.min.js.map 3 | -------------------------------------------------------------------------------- /dist/esm2015/ngx-image-editor.js: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation, NgModule } from '@angular/core'; 2 | import { FlexLayoutModule } from '@angular/flex-layout'; 3 | import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatIconModule, MatInputModule, MatMenuModule, MatProgressSpinnerModule, MatSliderModule, MatDialogModule, MatTabsModule, MatTooltipModule } from '@angular/material'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { CommonModule } from '@angular/common'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | 8 | /** 9 | * @fileoverview added by tsickle 10 | * @suppress {checkTypes} checked by tsc 11 | */ 12 | class NgxImageEditorComponent { 13 | constructor() { 14 | this.file = new EventEmitter(); 15 | this.zoomIn = 0; 16 | this.sliderValue = 0; 17 | this.loading = true; 18 | this.canvasFillColor = '#fff'; 19 | this.state = new EditorOptions(); 20 | } 21 | /** 22 | * @param {?} config 23 | * @return {?} 24 | */ 25 | set config(config) { 26 | this.state = config; 27 | } 28 | /** 29 | * @return {?} 30 | */ 31 | ngOnInit() { 32 | this.handleStateConfig(); 33 | } 34 | /** 35 | * @return {?} 36 | */ 37 | ngOnDestroy() { 38 | this.cropper.destroy(); 39 | } 40 | /** 41 | * @return {?} 42 | */ 43 | ngAfterViewInit() { 44 | // NOTE if we don't have a file meaning that loading the image will happen synchronously we can safely 45 | // call initializeCropper in ngAfterViewInit. otherwise if we are using the FileReader to load a base64 image 46 | // we need to call onloadend asynchronously.. 47 | if (!this.state.File && this.state.ImageUrl) { 48 | this.initializeCropper(); 49 | } 50 | } 51 | /** 52 | * @return {?} 53 | */ 54 | handleStateConfig() { 55 | this.state.ImageType = this.state.ImageType ? this.state.ImageType : 'image/jpeg'; 56 | if (this.state.ImageUrl) { 57 | this.state.File = null; 58 | this.previewImageURL = this.state.ImageUrl; 59 | } 60 | if (this.state.File) { 61 | this.state.ImageUrl = null; 62 | this.convertFileToBase64(this.state.File); 63 | } 64 | if (this.state.AspectRatios) { 65 | this.addRatios(this.state.AspectRatios); 66 | } 67 | else { 68 | this.ratios = NGX_DEFAULT_RATIOS; 69 | } 70 | if (!this.state.ImageUrl && !this.state.File) { 71 | console.error("Property ImageUrl or File is missing, Please provide an url or file in the config options."); 72 | } 73 | if (!this.state.ImageName) { 74 | console.error("Property ImageName is missing, Please provide a name for the image."); 75 | } 76 | } 77 | /** 78 | * @param {?} file 79 | * @return {?} 80 | */ 81 | convertFileToBase64(file) { 82 | const /** @type {?} */ reader = new FileReader(); 83 | reader.addEventListener("load", (e) => { 84 | this.previewImageURL = e.target["result"]; 85 | }, false); 86 | reader.readAsDataURL(file); 87 | reader.onloadend = (() => { 88 | // NOTE since getting the base64 image url happens asynchronously we need to initializeCropper after 89 | // the image has been loaded. 90 | this.initializeCropper(); 91 | }); 92 | } 93 | /** 94 | * @param {?} ratios 95 | * @return {?} 96 | */ 97 | addRatios(ratios) { 98 | this.ratios = []; 99 | ratios.forEach((ratioType) => { 100 | const /** @type {?} */ addedRation = NGX_DEFAULT_RATIOS.find((ratio) => { 101 | return ratio.text === ratioType; 102 | }); 103 | this.ratios.push(addedRation); 104 | }); 105 | } 106 | /** 107 | * @return {?} 108 | */ 109 | handleCrop() { 110 | this.loading = true; 111 | setTimeout(() => { 112 | this.croppedImage = this.cropper.getCroppedCanvas({ fillColor: this.canvasFillColor }) 113 | .toDataURL(this.state.ImageType); 114 | setTimeout(() => { 115 | this.imageWidth = this.croppedImg.nativeElement.width; 116 | this.imageHeight = this.croppedImg.nativeElement.height; 117 | }); 118 | this.cropper.getCroppedCanvas({ fillColor: this.canvasFillColor }).toBlob((blob) => { 119 | this.blob = blob; 120 | }); 121 | this.zoomIn = 1; 122 | this.loading = false; 123 | }, 2000); 124 | } 125 | /** 126 | * @return {?} 127 | */ 128 | undoCrop() { 129 | this.croppedImage = null; 130 | this.blob = null; 131 | setTimeout(() => { 132 | this.initializeCropper(); 133 | }, 100); 134 | } 135 | /** 136 | * @return {?} 137 | */ 138 | saveImage() { 139 | this.file.emit(new File([this.blob], this.state.ImageName, { type: this.state.ImageType })); 140 | } 141 | /** 142 | * @return {?} 143 | */ 144 | initializeCropper() { 145 | this.cropper = new Cropper(this.previewImage.nativeElement, { 146 | zoomOnWheel: true, 147 | viewMode: 0, 148 | center: true, 149 | ready: () => this.loading = false, 150 | dragMode: 'move', 151 | crop: (e) => { 152 | this.imageHeight = Math.round(e.detail.height); 153 | this.imageWidth = Math.round(e.detail.width); 154 | this.cropBoxWidth = Math.round(this.cropper.getCropBoxData().width); 155 | this.cropBoxHeight = Math.round(this.cropper.getCropBoxData().height); 156 | this.canvasWidth = Math.round(this.cropper.getCanvasData().width); 157 | this.canvasHeight = Math.round(this.cropper.getCanvasData().height); 158 | } 159 | }); 160 | this.setRatio(this.ratios[0].value); 161 | } 162 | /** 163 | * @param {?} value 164 | * @return {?} 165 | */ 166 | setRatio(value) { 167 | this.cropper.setAspectRatio(value); 168 | } 169 | /** 170 | * @param {?} input 171 | * @param {?=} zoom 172 | * @return {?} 173 | */ 174 | zoomChange(input, zoom) { 175 | if (this.croppedImage) { 176 | if (zoom) { 177 | zoom === 'zoomIn' ? this.zoomIn += 0.1 : this.zoomIn -= 0.1; 178 | } 179 | else { 180 | if (input < this.sliderValue) { 181 | this.zoomIn = -Math.abs(input / 100); 182 | } 183 | else { 184 | this.zoomIn = Math.abs(input / 100); 185 | } 186 | } 187 | if (this.zoomIn <= 0.1) { 188 | this.zoomIn = 0.1; 189 | } 190 | } 191 | else { 192 | if (zoom) { 193 | this.cropper.zoom(input); 194 | this.zoomIn = input; 195 | } 196 | else { 197 | if (input < this.sliderValue) { 198 | this.cropper.zoom(-Math.abs(input / 100)); 199 | } 200 | else { 201 | this.cropper.zoom(Math.abs(input / 100)); 202 | } 203 | if (input === 0) { 204 | this.cropper.zoom(-1); 205 | } 206 | } 207 | } 208 | if (!zoom) { 209 | this.sliderValue = input; 210 | } 211 | else { 212 | input > 0 ? this.sliderValue += Math.abs(input * 100) : this.sliderValue -= Math.abs(input * 100); 213 | } 214 | if (this.sliderValue < 0) { 215 | this.sliderValue = 0; 216 | } 217 | } 218 | /** 219 | * @param {?} canvasWidth 220 | * @return {?} 221 | */ 222 | setImageWidth(canvasWidth) { 223 | if (canvasWidth) { 224 | this.cropper.setCanvasData({ 225 | left: this.cropper.getCanvasData().left, 226 | top: this.cropper.getCanvasData().top, 227 | width: Math.round(canvasWidth), 228 | height: this.cropper.getCanvasData().height 229 | }); 230 | } 231 | } 232 | /** 233 | * @param {?} canvasHeight 234 | * @return {?} 235 | */ 236 | setImageHeight(canvasHeight) { 237 | if (canvasHeight) { 238 | this.cropper.setCanvasData({ 239 | left: this.cropper.getCanvasData().left, 240 | top: this.cropper.getCanvasData().top, 241 | width: this.cropper.getCanvasData().width, 242 | height: Math.round(canvasHeight) 243 | }); 244 | } 245 | } 246 | /** 247 | * @param {?} cropBoxWidth 248 | * @return {?} 249 | */ 250 | setCropBoxWidth(cropBoxWidth) { 251 | if (cropBoxWidth) { 252 | this.cropper.setCropBoxData({ 253 | left: this.cropper.getCropBoxData().left, 254 | top: this.cropper.getCropBoxData().top, 255 | width: Math.round(cropBoxWidth), 256 | height: this.cropper.getCropBoxData().height 257 | }); 258 | } 259 | } 260 | /** 261 | * @param {?} cropBoxHeight 262 | * @return {?} 263 | */ 264 | setCropBoxHeight(cropBoxHeight) { 265 | if (cropBoxHeight) { 266 | this.cropper.setCropBoxData({ 267 | left: this.cropper.getCropBoxData().left, 268 | top: this.cropper.getCropBoxData().top, 269 | width: this.cropper.getCropBoxData().width, 270 | height: Math.round(cropBoxHeight) 271 | }); 272 | } 273 | } 274 | /** 275 | * @return {?} 276 | */ 277 | centerCanvas() { 278 | const /** @type {?} */ cropBoxLeft = (this.cropper.getContainerData().width - this.cropper.getCropBoxData().width) / 2; 279 | const /** @type {?} */ cropBoxTop = (this.cropper.getContainerData().height - this.cropper.getCropBoxData().height) / 2; 280 | const /** @type {?} */ canvasLeft = (this.cropper.getContainerData().width - this.cropper.getCanvasData().width) / 2; 281 | const /** @type {?} */ canvasTop = (this.cropper.getContainerData().height - this.cropper.getCanvasData().height) / 2; 282 | this.cropper.setCropBoxData({ 283 | left: cropBoxLeft, 284 | top: cropBoxTop, 285 | width: this.cropper.getCropBoxData().width, 286 | height: this.cropper.getCropBoxData().height 287 | }); 288 | this.cropper.setCanvasData({ 289 | left: canvasLeft, 290 | top: canvasTop, 291 | width: this.cropper.getCanvasData().width, 292 | height: this.cropper.getCanvasData().height 293 | }); 294 | } 295 | } 296 | NgxImageEditorComponent.decorators = [ 297 | { type: Component, args: [{ 298 | selector: 'ngx-image-editor', 299 | template: ` 300 |
301 |
302 | photo 303 |
{{state.ImageName}}
304 | 308 | 315 |
316 | 317 |
323 | 324 |
328 | 330 |
331 |
332 | 333 |
334 | 337 |
338 |
339 | 340 |
341 | 342 |
348 | 349 |
350 |
Width: {{imageWidth}}px Height: {{imageHeight}}px
351 | 352 |
353 | 356 | 357 | 360 |
361 |
362 |
363 | 367 | 371 |
372 |
373 | 377 | 378 | open_with 379 | 380 | 381 | crop 382 | 383 | 384 | 385 | 389 | 390 | {{ratio.text}} 391 | 392 | 393 | 394 |
395 |
401 | 402 | 403 | 410 | 411 | 412 | 413 | 420 | 421 | 422 | 423 | 430 | 431 | 432 | 433 | 440 | 441 | 442 | 443 | 444 |
445 |
446 | 447 |
448 | 449 | `, 450 | styles: [` 451 | 452 | .ngx-image-editor-component .photo-editor-header { 453 | display: flex; 454 | justify-content: space-around; 455 | align-items: center; 456 | width: 100%; 457 | padding: 5px 0; 458 | z-index: 100; 459 | margin: 0; 460 | } 461 | 462 | .ngx-image-editor-component .photo-editor-header > .mat-icon { 463 | padding: 0 10px; 464 | } 465 | 466 | .ngx-image-editor-component .photo-editor-header > .file-name { 467 | flex: 1 1 100%; 468 | text-overflow: ellipsis; 469 | white-space: nowrap; 470 | overflow: hidden; 471 | } 472 | 473 | .ngx-image-editor-component mat-progress-spinner { 474 | position: absolute; 475 | } 476 | 477 | .ngx-image-editor-component .dialog-crop-container { 478 | width: 800px; 479 | height: 400px; 480 | overflow: hidden; 481 | } 482 | 483 | .ngx-image-editor-component .cropper-bg { 484 | background-image: none !important; 485 | } 486 | 487 | .ngx-image-editor-component .cropper-bg > .cropper-modal { 488 | opacity: 1 !important; 489 | background: none; 490 | } 491 | 492 | .ngx-image-editor-component .img-container { 493 | width: 800px !important; 494 | height: 400px !important; 495 | } 496 | 497 | .ngx-image-editor-component .cropped-image img { 498 | width: auto !important; 499 | height: auto !important; 500 | max-width: 800px !important; 501 | max-height: 400px !important; 502 | } 503 | 504 | .ngx-image-editor-component .dialog-button-actions { 505 | position: relative; 506 | padding: 0; 507 | } 508 | 509 | .ngx-image-editor-component .dialog-button-actions:last-child { 510 | margin: 0; 511 | } 512 | 513 | .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group { 514 | margin: 20px; 515 | } 516 | 517 | .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons { 518 | position: absolute; 519 | top: 50%; 520 | left: 50%; 521 | transform: translate(-50%, -50%); 522 | } 523 | 524 | .ngx-image-editor-component .dialog-button-actions > .canvas-config { 525 | padding: 5px; 526 | margin: 0 20px; 527 | } 528 | 529 | 530 | 531 | .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker { 532 | width: 200px !important; 533 | } 534 | 535 | 536 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom { 537 | display: flex; 538 | align-items: center; 539 | padding: 0 10px; 540 | } 541 | 542 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container { 543 | cursor: grab; 544 | } 545 | 546 | 547 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions { 548 | padding: 0 10px; 549 | font-size: 14px; 550 | width: 200px; 551 | max-width: 200px; 552 | } 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | `], 566 | encapsulation: ViewEncapsulation.None 567 | },] }, 568 | ]; 569 | /** @nocollapse */ 570 | NgxImageEditorComponent.ctorParameters = () => []; 571 | NgxImageEditorComponent.propDecorators = { 572 | "previewImage": [{ type: ViewChild, args: ['previewimg',] },], 573 | "croppedImg": [{ type: ViewChild, args: ['croppedImg',] },], 574 | "config": [{ type: Input },], 575 | "file": [{ type: Output },], 576 | }; 577 | /** 578 | * @record 579 | */ 580 | 581 | class EditorOptions { 582 | } 583 | /** 584 | * @record 585 | */ 586 | 587 | const NGX_DEFAULT_RATIOS = [ 588 | { 589 | value: 16 / 9, text: '16:9' 590 | }, 591 | { 592 | value: 4 / 3, text: '4:3' 593 | }, 594 | { 595 | value: 1 / 1, text: '1:1' 596 | }, 597 | { 598 | value: 2 / 3, text: '2:3' 599 | }, 600 | { 601 | value: 0 / 0, text: 'Default' 602 | } 603 | ]; 604 | 605 | /** 606 | * @fileoverview added by tsickle 607 | * @suppress {checkTypes} checked by tsc 608 | */ 609 | class NgxImageEditorModule { 610 | /** 611 | * @return {?} 612 | */ 613 | static forRoot() { 614 | return { 615 | ngModule: NgxImageEditorModule, 616 | }; 617 | } 618 | } 619 | NgxImageEditorModule.decorators = [ 620 | { type: NgModule, args: [{ 621 | imports: [ 622 | FormsModule, 623 | BrowserAnimationsModule, 624 | CommonModule, 625 | ReactiveFormsModule, 626 | FlexLayoutModule, 627 | MatButtonModule, 628 | MatIconModule, 629 | MatDialogModule, 630 | MatInputModule, 631 | MatMenuModule, 632 | MatProgressSpinnerModule, 633 | MatTabsModule, 634 | MatTooltipModule, 635 | MatButtonToggleModule, 636 | MatSliderModule, 637 | MatAutocompleteModule 638 | ], 639 | declarations: [ 640 | NgxImageEditorComponent 641 | ], 642 | exports: [NgxImageEditorComponent] 643 | },] }, 644 | ]; 645 | 646 | /** 647 | * @fileoverview added by tsickle 648 | * @suppress {checkTypes} checked by tsc 649 | */ 650 | 651 | /** 652 | * @fileoverview added by tsickle 653 | * @suppress {checkTypes} checked by tsc 654 | */ 655 | /** 656 | * Generated bundle index. Do not edit. 657 | */ 658 | 659 | export { NgxImageEditorModule, NgxImageEditorComponent, EditorOptions, NGX_DEFAULT_RATIOS }; 660 | //# sourceMappingURL=ngx-image-editor.js.map 661 | -------------------------------------------------------------------------------- /dist/esm5/ngx-image-editor.js: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation, NgModule } from '@angular/core'; 2 | import { FlexLayoutModule } from '@angular/flex-layout'; 3 | import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatIconModule, MatInputModule, MatMenuModule, MatProgressSpinnerModule, MatSliderModule, MatDialogModule, MatTabsModule, MatTooltipModule } from '@angular/material'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { CommonModule } from '@angular/common'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | 8 | var NgxImageEditorComponent = /** @class */ (function () { 9 | function NgxImageEditorComponent() { 10 | this.file = new EventEmitter(); 11 | this.zoomIn = 0; 12 | this.sliderValue = 0; 13 | this.loading = true; 14 | this.canvasFillColor = '#fff'; 15 | this.state = new EditorOptions(); 16 | } 17 | Object.defineProperty(NgxImageEditorComponent.prototype, "config", { 18 | set: function (config) { 19 | this.state = config; 20 | }, 21 | enumerable: true, 22 | configurable: true 23 | }); 24 | NgxImageEditorComponent.prototype.ngOnInit = function () { 25 | this.handleStateConfig(); 26 | }; 27 | NgxImageEditorComponent.prototype.ngOnDestroy = function () { 28 | this.cropper.destroy(); 29 | }; 30 | NgxImageEditorComponent.prototype.ngAfterViewInit = function () { 31 | if (!this.state.File && this.state.ImageUrl) { 32 | this.initializeCropper(); 33 | } 34 | }; 35 | NgxImageEditorComponent.prototype.handleStateConfig = function () { 36 | this.state.ImageType = this.state.ImageType ? this.state.ImageType : 'image/jpeg'; 37 | if (this.state.ImageUrl) { 38 | this.state.File = null; 39 | this.previewImageURL = this.state.ImageUrl; 40 | } 41 | if (this.state.File) { 42 | this.state.ImageUrl = null; 43 | this.convertFileToBase64(this.state.File); 44 | } 45 | if (this.state.AspectRatios) { 46 | this.addRatios(this.state.AspectRatios); 47 | } 48 | else { 49 | this.ratios = NGX_DEFAULT_RATIOS; 50 | } 51 | if (!this.state.ImageUrl && !this.state.File) { 52 | console.error("Property ImageUrl or File is missing, Please provide an url or file in the config options."); 53 | } 54 | if (!this.state.ImageName) { 55 | console.error("Property ImageName is missing, Please provide a name for the image."); 56 | } 57 | }; 58 | NgxImageEditorComponent.prototype.convertFileToBase64 = function (file) { 59 | var _this = this; 60 | var reader = new FileReader(); 61 | reader.addEventListener("load", function (e) { 62 | _this.previewImageURL = e.target["result"]; 63 | }, false); 64 | reader.readAsDataURL(file); 65 | reader.onloadend = (function () { 66 | _this.initializeCropper(); 67 | }); 68 | }; 69 | NgxImageEditorComponent.prototype.addRatios = function (ratios) { 70 | var _this = this; 71 | this.ratios = []; 72 | ratios.forEach(function (ratioType) { 73 | var addedRation = NGX_DEFAULT_RATIOS.find(function (ratio) { 74 | return ratio.text === ratioType; 75 | }); 76 | _this.ratios.push(addedRation); 77 | }); 78 | }; 79 | NgxImageEditorComponent.prototype.handleCrop = function () { 80 | var _this = this; 81 | this.loading = true; 82 | setTimeout(function () { 83 | _this.croppedImage = _this.cropper.getCroppedCanvas({ fillColor: _this.canvasFillColor }) 84 | .toDataURL(_this.state.ImageType); 85 | setTimeout(function () { 86 | _this.imageWidth = _this.croppedImg.nativeElement.width; 87 | _this.imageHeight = _this.croppedImg.nativeElement.height; 88 | }); 89 | _this.cropper.getCroppedCanvas({ fillColor: _this.canvasFillColor }).toBlob(function (blob) { 90 | _this.blob = blob; 91 | }); 92 | _this.zoomIn = 1; 93 | _this.loading = false; 94 | }, 2000); 95 | }; 96 | NgxImageEditorComponent.prototype.undoCrop = function () { 97 | var _this = this; 98 | this.croppedImage = null; 99 | this.blob = null; 100 | setTimeout(function () { 101 | _this.initializeCropper(); 102 | }, 100); 103 | }; 104 | NgxImageEditorComponent.prototype.saveImage = function () { 105 | this.file.emit(new File([this.blob], this.state.ImageName, { type: this.state.ImageType })); 106 | }; 107 | NgxImageEditorComponent.prototype.initializeCropper = function () { 108 | var _this = this; 109 | this.cropper = new Cropper(this.previewImage.nativeElement, { 110 | zoomOnWheel: true, 111 | viewMode: 0, 112 | center: true, 113 | ready: function () { return _this.loading = false; }, 114 | dragMode: 'move', 115 | crop: function (e) { 116 | _this.imageHeight = Math.round(e.detail.height); 117 | _this.imageWidth = Math.round(e.detail.width); 118 | _this.cropBoxWidth = Math.round(_this.cropper.getCropBoxData().width); 119 | _this.cropBoxHeight = Math.round(_this.cropper.getCropBoxData().height); 120 | _this.canvasWidth = Math.round(_this.cropper.getCanvasData().width); 121 | _this.canvasHeight = Math.round(_this.cropper.getCanvasData().height); 122 | } 123 | }); 124 | this.setRatio(this.ratios[0].value); 125 | }; 126 | NgxImageEditorComponent.prototype.setRatio = function (value) { 127 | this.cropper.setAspectRatio(value); 128 | }; 129 | NgxImageEditorComponent.prototype.zoomChange = function (input, zoom) { 130 | if (this.croppedImage) { 131 | if (zoom) { 132 | zoom === 'zoomIn' ? this.zoomIn += 0.1 : this.zoomIn -= 0.1; 133 | } 134 | else { 135 | if (input < this.sliderValue) { 136 | this.zoomIn = -Math.abs(input / 100); 137 | } 138 | else { 139 | this.zoomIn = Math.abs(input / 100); 140 | } 141 | } 142 | if (this.zoomIn <= 0.1) { 143 | this.zoomIn = 0.1; 144 | } 145 | } 146 | else { 147 | if (zoom) { 148 | this.cropper.zoom(input); 149 | this.zoomIn = input; 150 | } 151 | else { 152 | if (input < this.sliderValue) { 153 | this.cropper.zoom(-Math.abs(input / 100)); 154 | } 155 | else { 156 | this.cropper.zoom(Math.abs(input / 100)); 157 | } 158 | if (input === 0) { 159 | this.cropper.zoom(-1); 160 | } 161 | } 162 | } 163 | if (!zoom) { 164 | this.sliderValue = input; 165 | } 166 | else { 167 | input > 0 ? this.sliderValue += Math.abs(input * 100) : this.sliderValue -= Math.abs(input * 100); 168 | } 169 | if (this.sliderValue < 0) { 170 | this.sliderValue = 0; 171 | } 172 | }; 173 | NgxImageEditorComponent.prototype.setImageWidth = function (canvasWidth) { 174 | if (canvasWidth) { 175 | this.cropper.setCanvasData({ 176 | left: this.cropper.getCanvasData().left, 177 | top: this.cropper.getCanvasData().top, 178 | width: Math.round(canvasWidth), 179 | height: this.cropper.getCanvasData().height 180 | }); 181 | } 182 | }; 183 | NgxImageEditorComponent.prototype.setImageHeight = function (canvasHeight) { 184 | if (canvasHeight) { 185 | this.cropper.setCanvasData({ 186 | left: this.cropper.getCanvasData().left, 187 | top: this.cropper.getCanvasData().top, 188 | width: this.cropper.getCanvasData().width, 189 | height: Math.round(canvasHeight) 190 | }); 191 | } 192 | }; 193 | NgxImageEditorComponent.prototype.setCropBoxWidth = function (cropBoxWidth) { 194 | if (cropBoxWidth) { 195 | this.cropper.setCropBoxData({ 196 | left: this.cropper.getCropBoxData().left, 197 | top: this.cropper.getCropBoxData().top, 198 | width: Math.round(cropBoxWidth), 199 | height: this.cropper.getCropBoxData().height 200 | }); 201 | } 202 | }; 203 | NgxImageEditorComponent.prototype.setCropBoxHeight = function (cropBoxHeight) { 204 | if (cropBoxHeight) { 205 | this.cropper.setCropBoxData({ 206 | left: this.cropper.getCropBoxData().left, 207 | top: this.cropper.getCropBoxData().top, 208 | width: this.cropper.getCropBoxData().width, 209 | height: Math.round(cropBoxHeight) 210 | }); 211 | } 212 | }; 213 | NgxImageEditorComponent.prototype.centerCanvas = function () { 214 | var cropBoxLeft = (this.cropper.getContainerData().width - this.cropper.getCropBoxData().width) / 2; 215 | var cropBoxTop = (this.cropper.getContainerData().height - this.cropper.getCropBoxData().height) / 2; 216 | var canvasLeft = (this.cropper.getContainerData().width - this.cropper.getCanvasData().width) / 2; 217 | var canvasTop = (this.cropper.getContainerData().height - this.cropper.getCanvasData().height) / 2; 218 | this.cropper.setCropBoxData({ 219 | left: cropBoxLeft, 220 | top: cropBoxTop, 221 | width: this.cropper.getCropBoxData().width, 222 | height: this.cropper.getCropBoxData().height 223 | }); 224 | this.cropper.setCanvasData({ 225 | left: canvasLeft, 226 | top: canvasTop, 227 | width: this.cropper.getCanvasData().width, 228 | height: this.cropper.getCanvasData().height 229 | }); 230 | }; 231 | return NgxImageEditorComponent; 232 | }()); 233 | NgxImageEditorComponent.decorators = [ 234 | { type: Component, args: [{ 235 | selector: 'ngx-image-editor', 236 | template: "\n
\n
\n photo\n
{{state.ImageName}}
\n \n \n
\n\n
\n \n \n \n
\n \n \n
\n \n
\n
\n \n
\n\n \n\n
\n
Width: {{imageWidth}}px Height: {{imageHeight}}px
\n \n
\n \n \n \n
\n
\n
\n \n \n
\n
\n \n \n open_with\n \n \n crop\n \n \n\n \n \n {{ratio.text}}\n \n \n\n
\n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n\n \n \n\n \n\n ", 237 | styles: ["\n\n .ngx-image-editor-component .photo-editor-header {\n display: flex;\n justify-content: space-around;\n align-items: center;\n width: 100%;\n padding: 5px 0;\n z-index: 100;\n margin: 0;\n }\n\n .ngx-image-editor-component .photo-editor-header > .mat-icon {\n padding: 0 10px;\n }\n\n .ngx-image-editor-component .photo-editor-header > .file-name {\n flex: 1 1 100%;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n\n .ngx-image-editor-component mat-progress-spinner {\n position: absolute;\n }\n\n .ngx-image-editor-component .dialog-crop-container {\n width: 800px;\n height: 400px;\n overflow: hidden;\n }\n\n .ngx-image-editor-component .cropper-bg {\n background-image: none !important;\n }\n\n .ngx-image-editor-component .cropper-bg > .cropper-modal {\n opacity: 1 !important;\n background: none;\n }\n\n .ngx-image-editor-component .img-container {\n width: 800px !important;\n height: 400px !important;\n }\n\n .ngx-image-editor-component .cropped-image img {\n width: auto !important;\n height: auto !important;\n max-width: 800px !important;\n max-height: 400px !important;\n }\n\n .ngx-image-editor-component .dialog-button-actions {\n position: relative;\n padding: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions:last-child {\n margin: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group {\n margin: 20px;\n }\n\n .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config {\n padding: 5px;\n margin: 0 20px;\n }\n\n \n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker {\n width: 200px !important;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom {\n display: flex;\n align-items: center;\n padding: 0 10px;\n }\n \n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container {\n cursor: grab;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions {\n padding: 0 10px;\n font-size: 14px;\n width: 200px;\n max-width: 200px;\n }\n\n \n\n\n\n\n\n\n\n\n\n\n "], 238 | encapsulation: ViewEncapsulation.None 239 | },] }, 240 | ]; 241 | NgxImageEditorComponent.ctorParameters = function () { return []; }; 242 | NgxImageEditorComponent.propDecorators = { 243 | "previewImage": [{ type: ViewChild, args: ['previewimg',] },], 244 | "croppedImg": [{ type: ViewChild, args: ['croppedImg',] },], 245 | "config": [{ type: Input },], 246 | "file": [{ type: Output },], 247 | }; 248 | var EditorOptions = /** @class */ (function () { 249 | function EditorOptions() { 250 | } 251 | return EditorOptions; 252 | }()); 253 | var NGX_DEFAULT_RATIOS = [ 254 | { 255 | value: 16 / 9, text: '16:9' 256 | }, 257 | { 258 | value: 4 / 3, text: '4:3' 259 | }, 260 | { 261 | value: 1 / 1, text: '1:1' 262 | }, 263 | { 264 | value: 2 / 3, text: '2:3' 265 | }, 266 | { 267 | value: 0 / 0, text: 'Default' 268 | } 269 | ]; 270 | var NgxImageEditorModule = /** @class */ (function () { 271 | function NgxImageEditorModule() { 272 | } 273 | NgxImageEditorModule.forRoot = function () { 274 | return { 275 | ngModule: NgxImageEditorModule, 276 | }; 277 | }; 278 | return NgxImageEditorModule; 279 | }()); 280 | NgxImageEditorModule.decorators = [ 281 | { type: NgModule, args: [{ 282 | imports: [ 283 | FormsModule, 284 | BrowserAnimationsModule, 285 | CommonModule, 286 | ReactiveFormsModule, 287 | FlexLayoutModule, 288 | MatButtonModule, 289 | MatIconModule, 290 | MatDialogModule, 291 | MatInputModule, 292 | MatMenuModule, 293 | MatProgressSpinnerModule, 294 | MatTabsModule, 295 | MatTooltipModule, 296 | MatButtonToggleModule, 297 | MatSliderModule, 298 | MatAutocompleteModule 299 | ], 300 | declarations: [ 301 | NgxImageEditorComponent 302 | ], 303 | exports: [NgxImageEditorComponent] 304 | },] }, 305 | ]; 306 | 307 | export { NgxImageEditorModule, NgxImageEditorComponent, EditorOptions, NGX_DEFAULT_RATIOS }; 308 | //# sourceMappingURL=ngx-image-editor.js.map 309 | -------------------------------------------------------------------------------- /dist/ngx-image-editor.component.d.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, EventEmitter, OnDestroy, OnInit } from '@angular/core'; 2 | export declare class NgxImageEditorComponent implements AfterViewInit, OnInit, OnDestroy { 3 | state: EditorOptions; 4 | cropper: any; 5 | croppedImage: string; 6 | imageWidth: number; 7 | imageHeight: number; 8 | canvasWidth: number; 9 | canvasHeight: number; 10 | cropBoxWidth: number; 11 | cropBoxHeight: number; 12 | canvasFillColor: string; 13 | blob: Blob; 14 | loading: boolean; 15 | private zoomIn; 16 | sliderValue: number; 17 | ratios: NgxAspectRatio[]; 18 | previewImageURL: any; 19 | previewImage: any; 20 | croppedImg: any; 21 | config: EditorOptions; 22 | file: EventEmitter; 23 | constructor(); 24 | ngOnInit(): void; 25 | ngOnDestroy(): void; 26 | ngAfterViewInit(): void; 27 | private handleStateConfig(); 28 | private convertFileToBase64(file); 29 | private addRatios(ratios); 30 | handleCrop(): void; 31 | undoCrop(): void; 32 | saveImage(): void; 33 | private initializeCropper(); 34 | setRatio(value: any): void; 35 | zoomChange(input: any, zoom?: string): void; 36 | setImageWidth(canvasWidth: number): void; 37 | setImageHeight(canvasHeight: number): void; 38 | setCropBoxWidth(cropBoxWidth: number): void; 39 | setCropBoxHeight(cropBoxHeight: number): void; 40 | centerCanvas(): void; 41 | } 42 | export interface IEditorOptions { 43 | ImageName: string; 44 | ImageUrl?: string; 45 | ImageType?: string; 46 | File?: File; 47 | AspectRatios?: Array; 48 | } 49 | export declare type RatioType = "16:9" | '4:3' | '1:1' | '2:3' | 'Default'; 50 | export declare class EditorOptions implements IEditorOptions { 51 | ImageName: string; 52 | ImageUrl?: string; 53 | ImageType?: string; 54 | File?: File; 55 | AspectRatios?: Array; 56 | } 57 | export interface NgxAspectRatio { 58 | value: number; 59 | text: RatioType; 60 | } 61 | export declare const NGX_DEFAULT_RATIOS: Array; 62 | -------------------------------------------------------------------------------- /dist/ngx-image-editor.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated bundle index. Do not edit. 3 | */ 4 | export * from './public_api'; 5 | -------------------------------------------------------------------------------- /dist/ngx-image-editor.metadata.json: -------------------------------------------------------------------------------- 1 | {"__symbolic":"module","version":4,"metadata":{"NgxImageEditorModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":16,"character":1},"arguments":[{"imports":[{"__symbolic":"reference","module":"@angular/forms","name":"FormsModule","line":19,"character":8},{"__symbolic":"reference","module":"@angular/platform-browser/animations","name":"BrowserAnimationsModule","line":20,"character":8},{"__symbolic":"reference","module":"@angular/common","name":"CommonModule","line":21,"character":8},{"__symbolic":"reference","module":"@angular/forms","name":"ReactiveFormsModule","line":22,"character":8},{"__symbolic":"reference","module":"@angular/flex-layout","name":"FlexLayoutModule","line":23,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatButtonModule","line":24,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatIconModule","line":25,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatDialogModule","line":26,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatInputModule","line":27,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatMenuModule","line":28,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatProgressSpinnerModule","line":29,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatTabsModule","line":30,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatTooltipModule","line":31,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatButtonToggleModule","line":32,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatSliderModule","line":33,"character":8},{"__symbolic":"reference","module":"@angular/material","name":"MatAutocompleteModule","line":34,"character":8}],"declarations":[{"__symbolic":"reference","name":"NgxImageEditorComponent"}],"exports":[{"__symbolic":"reference","name":"NgxImageEditorComponent"}]}]}],"members":{},"statics":{"forRoot":{"__symbolic":"function","parameters":[],"value":{"ngModule":{"__symbolic":"reference","name":"NgxImageEditorModule"}}}}},"NgxImageEditorComponent":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component","line":14,"character":1},"arguments":[{"selector":"ngx-image-editor","template":"\n
\n
\n photo\n
{{state.ImageName}}
\n \n \n
\n\n
\n \n \n \n
\n \n \n
\n \n
\n
\n \n
\n\n \n\n
\n
Width: {{imageWidth}}px Height: {{imageHeight}}px
\n \n
\n \n \n \n
\n
\n
\n \n \n
\n
\n \n \n open_with\n \n \n crop\n \n \n\n \n \n {{ratio.text}}\n \n \n\n
\n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n\n \n \n\n \n\n ","styles":["\n\n .ngx-image-editor-component .photo-editor-header {\n display: flex;\n justify-content: space-around;\n align-items: center;\n width: 100%;\n padding: 5px 0;\n z-index: 100;\n margin: 0;\n }\n\n .ngx-image-editor-component .photo-editor-header > .mat-icon {\n padding: 0 10px;\n }\n\n .ngx-image-editor-component .photo-editor-header > .file-name {\n flex: 1 1 100%;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n\n .ngx-image-editor-component mat-progress-spinner {\n position: absolute;\n }\n\n .ngx-image-editor-component .dialog-crop-container {\n width: 800px;\n height: 400px;\n overflow: hidden;\n }\n\n .ngx-image-editor-component .cropper-bg {\n background-image: none !important;\n }\n\n .ngx-image-editor-component .cropper-bg > .cropper-modal {\n opacity: 1 !important;\n background: none;\n }\n\n .ngx-image-editor-component .img-container {\n width: 800px !important;\n height: 400px !important;\n }\n\n .ngx-image-editor-component .cropped-image img {\n width: auto !important;\n height: auto !important;\n max-width: 800px !important;\n max-height: 400px !important;\n }\n\n .ngx-image-editor-component .dialog-button-actions {\n position: relative;\n padding: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions:last-child {\n margin: 0;\n }\n\n .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group {\n margin: 20px;\n }\n\n .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config {\n padding: 5px;\n margin: 0 20px;\n }\n\n \n\n .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker {\n width: 200px !important;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom {\n display: flex;\n align-items: center;\n padding: 0 10px;\n }\n \n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container {\n cursor: grab;\n }\n \n\n .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions {\n padding: 0 10px;\n font-size: 14px;\n width: 200px;\n max-width: 200px;\n }\n\n \n\n\n\n\n\n\n\n\n\n\n "],"encapsulation":{"__symbolic":"select","expression":{"__symbolic":"reference","module":"@angular/core","name":"ViewEncapsulation","line":283,"character":17},"member":"None"}}]}],"members":{"previewImage":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"ViewChild","line":305,"character":3},"arguments":["previewimg"]}]}],"croppedImg":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"ViewChild","line":308,"character":3},"arguments":["croppedImg"]}]}],"config":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input","line":311,"character":3}}]}],"file":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Output","line":316,"character":3}}]}],"__ctor__":[{"__symbolic":"constructor"}],"ngOnInit":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}],"ngAfterViewInit":[{"__symbolic":"method"}],"handleStateConfig":[{"__symbolic":"method"}],"convertFileToBase64":[{"__symbolic":"method"}],"addRatios":[{"__symbolic":"method"}],"handleCrop":[{"__symbolic":"method"}],"undoCrop":[{"__symbolic":"method"}],"saveImage":[{"__symbolic":"method"}],"initializeCropper":[{"__symbolic":"method"}],"setRatio":[{"__symbolic":"method"}],"zoomChange":[{"__symbolic":"method"}],"setImageWidth":[{"__symbolic":"method"}],"setImageHeight":[{"__symbolic":"method"}],"setCropBoxWidth":[{"__symbolic":"method"}],"setCropBoxHeight":[{"__symbolic":"method"}],"centerCanvas":[{"__symbolic":"method"}]}},"IEditorOptions":{"__symbolic":"interface"},"RatioType":{"__symbolic":"interface"},"EditorOptions":{"__symbolic":"class","members":{}},"NgxAspectRatio":{"__symbolic":"interface"},"NGX_DEFAULT_RATIOS":[{"value":1.7777777777777777,"text":"16:9"},{"value":1.3333333333333333,"text":"4:3"},{"value":1,"text":"1:1"},{"value":0.6666666666666666,"text":"2:3"},{"value":null,"text":"Default"}]},"origins":{"NgxImageEditorModule":"./ngx-image-editor.module","NgxImageEditorComponent":"./ngx-image-editor.component","IEditorOptions":"./ngx-image-editor.component","RatioType":"./ngx-image-editor.component","EditorOptions":"./ngx-image-editor.component","NgxAspectRatio":"./ngx-image-editor.component","NGX_DEFAULT_RATIOS":"./ngx-image-editor.component"},"importAs":"ngx-image-editor"} -------------------------------------------------------------------------------- /dist/ngx-image-editor.module.d.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | export * from './ngx-image-editor.component'; 3 | export declare class NgxImageEditorModule { 4 | static forRoot(): ModuleWithProviders; 5 | } 6 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-image-editor", 3 | "version": "1.0.0-semantic-release", 4 | "description": "Angular 5 Image Editor", 5 | "main": "bundles/ngx-image-editor.umd.js", 6 | "module": "esm5/ngx-image-editor.js", 7 | "typings": "ngx-image-editor.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/Centroida/ngx-image-editor" 11 | }, 12 | "keywords": [ 13 | "Angular 2", 14 | "Angular 4", 15 | "Angular 5", 16 | "Angular 6", 17 | "Angular", 18 | "ng", 19 | "Editor", 20 | "Image Editor", 21 | "Image Preview", 22 | "Cropper" 23 | ], 24 | "author": "Centroida", 25 | "license": "MIT", 26 | "homepage": "https://github.com/Centroida/ngx-image-editor", 27 | "devDependencies": { 28 | "@angular/animations": "6.0.0", 29 | "@angular/cdk": "^5.2.4", 30 | "@angular/cli": "6.0.0", 31 | "@angular/common": "6.0.0", 32 | "@angular/compiler": "6.0.1", 33 | "@angular/compiler-cli": "6.0.1", 34 | "@angular/core": "6.0.0", 35 | "@angular/flex-layout": "^5.0.0-beta.14", 36 | "@angular/forms": "6.0.0", 37 | "@angular/http": "6.0.0", 38 | "@angular/material": "^5.2.4", 39 | "@angular/platform-browser": "6.0.0", 40 | "@angular/platform-browser-dynamic": "6.0.0", 41 | "@types/core-js": "0.9.46", 42 | "@types/node": "~6.0.60", 43 | "codelyzer": "^4.2.1", 44 | "core-js": "^2.5.3", 45 | "ng-packagr": "^2.4.4", 46 | "rxjs": "^6.1.0", 47 | "semantic-release": "^15.4.1", 48 | "ts-node": "^5.0.1", 49 | "tslint": "^5.9.1", 50 | "typescript": "2.7.2", 51 | "zone.js": "~0.8.26" 52 | }, 53 | "peerDependencies": { 54 | "@types/cropperjs": "^1.1.3", 55 | "cropperjs": "^1.0.0", 56 | "ng-packagr": "^2.4.4", 57 | "rxjs-compat": "^6.0.0-rc.0", 58 | "tsickle": "^0.28.0", 59 | "tslib": "^1.9.0" 60 | }, 61 | "release": { 62 | "debug": false, 63 | "pkgRoot": "dist", 64 | "analyzeCommits": { 65 | "preset": "angular", 66 | "releaseRules": [ 67 | { 68 | "type": "docs", 69 | "scope": "README.md", 70 | "release": "patch" 71 | }, 72 | { 73 | "type": "refactor", 74 | "scope": "/src", 75 | "release": "minor" 76 | }, 77 | { 78 | "type": "refactor", 79 | "release": "patch" 80 | } 81 | ] 82 | } 83 | }, 84 | "es2015": "esm2015/ngx-image-editor.js", 85 | "metadata": "ngx-image-editor.metadata.json", 86 | "dependencies": { 87 | "tslib": "^1.9.0" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dist/public_api.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./ngx-image-editor.module"; 2 | -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/public_api.ts" 5 | } 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-image-editor", 3 | "version": "1.0.0-semantic-release", 4 | "description": "Angular 5 Image Editor", 5 | "scripts": { 6 | "test": "echo \"Test success\"", 7 | "clean": "rm -rf dist ", 8 | "build": "ng-packagr -p ng-package.json", 9 | "semantic-release": "npm run clean && npm run build && semantic-release --prepare && semantic-release --publish " 10 | }, 11 | "main": "./bundles/ng.umd.js", 12 | "module": "./ngx-image-editor.es5.js", 13 | "typings": "./ngx-image-editor.d.ts", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Centroida/ngx-image-editor" 17 | }, 18 | "keywords": [ 19 | "Angular 2", 20 | "Angular 4", 21 | "Angular 5", 22 | "Angular 6", 23 | "Angular", 24 | "ng", 25 | "Editor", 26 | "Image Editor", 27 | "Image Preview", 28 | "Cropper" 29 | ], 30 | "author": "Centroida", 31 | "license": "MIT", 32 | "homepage": "https://github.com/Centroida/ngx-image-editor", 33 | "devDependencies": { 34 | "@angular/animations": "6.0.0", 35 | "@angular/cdk": "^5.2.4", 36 | "@angular/cli": "6.0.0", 37 | "@angular/common": "6.0.0", 38 | "@angular/compiler": "6.0.1", 39 | "@angular/compiler-cli": "6.0.1", 40 | "@angular/core": "6.0.0", 41 | "@angular/flex-layout": "^5.0.0-beta.14", 42 | "@angular/forms": "6.0.0", 43 | "@angular/http": "6.0.0", 44 | "@angular/material": "^5.2.4", 45 | "@angular/platform-browser": "6.0.0", 46 | "@angular/platform-browser-dynamic": "6.0.0", 47 | "@types/core-js": "0.9.46", 48 | "@types/node": "~6.0.60", 49 | "codelyzer": "^4.2.1", 50 | "core-js": "^2.5.3", 51 | "ng-packagr": "^2.4.4", 52 | "rxjs": "^6.1.0", 53 | "semantic-release": "^15.4.1", 54 | "ts-node": "^5.0.1", 55 | "tslint": "^5.9.1", 56 | "typescript": "2.7.2", 57 | "zone.js": "~0.8.26" 58 | }, 59 | "peerDependencies": { 60 | "@types/cropperjs": "^1.1.3", 61 | "cropperjs": "^1.0.0", 62 | "ng-packagr": "^2.4.4", 63 | "rxjs-compat": "^6.0.0-rc.0", 64 | "tsickle": "^0.28.0", 65 | "tslib": "^1.9.0" 66 | }, 67 | "release": { 68 | "debug": false, 69 | "pkgRoot": "dist", 70 | "analyzeCommits": { 71 | "preset": "angular", 72 | "releaseRules": [ 73 | { 74 | "type": "docs", 75 | "scope": "README.md", 76 | "release": "patch" 77 | }, 78 | { 79 | "type": "refactor", 80 | "scope": "/src", 81 | "release": "minor" 82 | }, 83 | { 84 | "type": "refactor", 85 | "release": "patch" 86 | } 87 | ] 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hggeorgiev/ngx-image-editor/b9b49316ec0f621d356b3bba5ac164487fe4578d/src/.DS_Store -------------------------------------------------------------------------------- /src/ngx-image-editor.component.d.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, EventEmitter, OnDestroy, OnInit } from '@angular/core'; 2 | export declare class NgxImageEditorComponent implements AfterViewInit, OnInit, OnDestroy { 3 | state: EditorOptions; 4 | cropper: any; 5 | croppedImage: string; 6 | imageWidth: number; 7 | imageHeight: number; 8 | canvasWidth: number; 9 | canvasHeight: number; 10 | cropBoxWidth: number; 11 | cropBoxHeight: number; 12 | canvasFillColor: string; 13 | blob: Blob; 14 | loading: boolean; 15 | private zoomIn; 16 | sliderValue: number; 17 | ratios: NgxAspectRatio[]; 18 | previewImageURL: any; 19 | previewImage: any; 20 | croppedImg: any; 21 | config: EditorOptions; 22 | file: EventEmitter; 23 | constructor(); 24 | ngOnInit(): void; 25 | ngOnDestroy(): void; 26 | ngAfterViewInit(): void; 27 | private handleStateConfig(); 28 | private convertFileToBase64(file); 29 | private addRatios(ratios); 30 | handleCrop(): void; 31 | undoCrop(): void; 32 | saveImage(): void; 33 | private initializeCropper(); 34 | setRatio(value: any): void; 35 | zoomChange(input: any, zoom?: string): void; 36 | setImageWidth(canvasWidth: number): void; 37 | setImageHeight(canvasHeight: number): void; 38 | setCropBoxWidth(cropBoxWidth: number): void; 39 | setCropBoxHeight(cropBoxHeight: number): void; 40 | centerCanvas(): void; 41 | } 42 | export interface IEditorOptions { 43 | ImageName: string; 44 | ImageUrl?: string; 45 | ImageType?: string; 46 | File?: File; 47 | AspectRatios?: Array; 48 | } 49 | export declare type RatioType = "16:9" | '4:3' | '1:1' | '2:3' | 'Default'; 50 | export declare class EditorOptions implements IEditorOptions { 51 | ImageName: string; 52 | ImageUrl?: string; 53 | ImageType?: string; 54 | File?: File; 55 | AspectRatios?: Array; 56 | } 57 | export interface NgxAspectRatio { 58 | value: number; 59 | text: RatioType; 60 | } 61 | export declare const NGX_DEFAULT_RATIOS: Array; 62 | -------------------------------------------------------------------------------- /src/ngx-image-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | Component, 4 | EventEmitter, 5 | Input, 6 | OnDestroy, 7 | OnInit, 8 | Output, 9 | ViewChild, 10 | ViewEncapsulation 11 | } from '@angular/core'; 12 | 13 | declare const Cropper: any; 14 | 15 | @Component({ 16 | selector: 'ngx-image-editor', 17 | template: ` 18 |
19 |
20 | photo 21 |
{{state.ImageName}}
22 | 26 | 33 |
34 | 35 |
41 | 42 |
46 | 48 |
49 |
50 | 51 |
52 | 55 |
56 |
57 | 58 |
59 | 60 |
66 | 67 |
68 |
Width: {{imageWidth}}px Height: {{imageHeight}}px
69 | 70 |
71 | 74 | 75 | 78 |
79 |
80 |
81 | 85 | 89 |
90 |
91 | 95 | 96 | open_with 97 | 98 | 99 | crop 100 | 101 | 102 | 103 | 107 | 108 | {{ratio.text}} 109 | 110 | 111 | 112 |
113 |
119 | 120 | 121 | 128 | 129 | 130 | 131 | 138 | 139 | 140 | 141 | 148 | 149 | 150 | 151 | 158 | 159 | 160 | 161 | 162 |
163 |
164 | 165 |
166 | 167 | `, 168 | styles: [` 169 | 170 | .ngx-image-editor-component .photo-editor-header { 171 | display: flex; 172 | justify-content: space-around; 173 | align-items: center; 174 | width: 100%; 175 | padding: 5px 0; 176 | z-index: 100; 177 | margin: 0; 178 | } 179 | 180 | .ngx-image-editor-component .photo-editor-header > .mat-icon { 181 | padding: 0 10px; 182 | } 183 | 184 | .ngx-image-editor-component .photo-editor-header > .file-name { 185 | flex: 1 1 100%; 186 | text-overflow: ellipsis; 187 | white-space: nowrap; 188 | overflow: hidden; 189 | } 190 | 191 | .ngx-image-editor-component mat-progress-spinner { 192 | position: absolute; 193 | } 194 | 195 | .ngx-image-editor-component .dialog-crop-container { 196 | width: 800px; 197 | height: 400px; 198 | overflow: hidden; 199 | } 200 | 201 | .ngx-image-editor-component .cropper-bg { 202 | background-image: none !important; 203 | } 204 | 205 | .ngx-image-editor-component .cropper-bg > .cropper-modal { 206 | opacity: 1 !important; 207 | background: none; 208 | } 209 | 210 | .ngx-image-editor-component .img-container { 211 | width: 800px !important; 212 | height: 400px !important; 213 | } 214 | 215 | .ngx-image-editor-component .cropped-image img { 216 | width: auto !important; 217 | height: auto !important; 218 | max-width: 800px !important; 219 | max-height: 400px !important; 220 | } 221 | 222 | .ngx-image-editor-component .dialog-button-actions { 223 | position: relative; 224 | padding: 0; 225 | } 226 | 227 | .ngx-image-editor-component .dialog-button-actions:last-child { 228 | margin: 0; 229 | } 230 | 231 | .ngx-image-editor-component .dialog-button-actions > DIV mat-button-toggle-group { 232 | margin: 20px; 233 | } 234 | 235 | .ngx-image-editor-component .dialog-button-actions .cropped-image-buttons { 236 | position: absolute; 237 | top: 50%; 238 | left: 50%; 239 | transform: translate(-50%, -50%); 240 | } 241 | 242 | .ngx-image-editor-component .dialog-button-actions > .canvas-config { 243 | padding: 5px; 244 | margin: 0 20px; 245 | } 246 | 247 | 248 | 249 | .ngx-image-editor-component .dialog-button-actions > .canvas-config md2-colorpicker { 250 | width: 200px !important; 251 | } 252 | 253 | 254 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom { 255 | display: flex; 256 | align-items: center; 257 | padding: 0 10px; 258 | } 259 | 260 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-zoom .mat-slider-horizontal .mat-slider-wrapper .mat-slider-thumb-container { 261 | cursor: grab; 262 | } 263 | 264 | 265 | .ngx-image-editor-component .dialog-button-actions .image-detail-toolbar > .image-dimensions { 266 | padding: 0 10px; 267 | font-size: 14px; 268 | width: 200px; 269 | max-width: 200px; 270 | } 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | `], 284 | encapsulation: ViewEncapsulation.None 285 | }) 286 | 287 | export class NgxImageEditorComponent implements AfterViewInit, OnInit, OnDestroy { 288 | 289 | public state: EditorOptions; 290 | public cropper: any; 291 | public croppedImage: string; 292 | public imageWidth: number; 293 | public imageHeight: number; 294 | public canvasWidth: number; 295 | public canvasHeight: number; 296 | public cropBoxWidth: number; 297 | public cropBoxHeight: number; 298 | public canvasFillColor: string; 299 | public blob: Blob; 300 | public loading: boolean; 301 | private zoomIn: number; 302 | public sliderValue: number; 303 | public ratios: NgxAspectRatio[]; 304 | public previewImageURL: any; 305 | 306 | @ViewChild('previewimg') 307 | public previewImage: any; 308 | 309 | @ViewChild('croppedImg') 310 | public croppedImg: any; 311 | 312 | @Input() 313 | public set config(config: EditorOptions) { 314 | this.state = config; 315 | } 316 | 317 | @Output() 318 | public file: EventEmitter = new EventEmitter(); 319 | 320 | public constructor() { 321 | this.zoomIn = 0; 322 | this.sliderValue = 0; 323 | this.loading = true; 324 | this.canvasFillColor = '#fff'; 325 | this.state = new EditorOptions(); 326 | } 327 | 328 | public ngOnInit() { 329 | this.handleStateConfig(); 330 | } 331 | 332 | public ngOnDestroy() { 333 | this.cropper.destroy(); 334 | } 335 | 336 | public ngAfterViewInit(): void { 337 | 338 | // NOTE if we don't have a file meaning that loading the image will happen synchronously we can safely 339 | // call initializeCropper in ngAfterViewInit. otherwise if we are using the FileReader to load a base64 image 340 | // we need to call onloadend asynchronously.. 341 | if (!this.state.File && this.state.ImageUrl) { 342 | this.initializeCropper(); 343 | } 344 | } 345 | 346 | private handleStateConfig() { 347 | this.state.ImageType = this.state.ImageType ? this.state.ImageType : 'image/jpeg'; 348 | 349 | if (this.state.ImageUrl) { 350 | this.state.File = null; 351 | this.previewImageURL = this.state.ImageUrl; 352 | } 353 | 354 | if (this.state.File) { 355 | this.state.ImageUrl = null; 356 | this.convertFileToBase64(this.state.File); 357 | } 358 | 359 | if (this.state.AspectRatios) { 360 | this.addRatios(this.state.AspectRatios); 361 | } else { 362 | this.ratios = NGX_DEFAULT_RATIOS; 363 | } 364 | 365 | 366 | if (!this.state.ImageUrl && !this.state.File) { 367 | console.error("Property ImageUrl or File is missing, Please provide an url or file in the config options."); 368 | } 369 | 370 | if (!this.state.ImageName) { 371 | console.error("Property ImageName is missing, Please provide a name for the image."); 372 | } 373 | } 374 | 375 | private convertFileToBase64(file: File) { 376 | const reader = new FileReader(); 377 | reader.addEventListener("load", (e: any) => { 378 | this.previewImageURL = e.target["result"]; 379 | }, false); 380 | reader.readAsDataURL(file); 381 | reader.onloadend = (() => { 382 | // NOTE since getting the base64 image url happens asynchronously we need to initializeCropper after 383 | // the image has been loaded. 384 | this.initializeCropper(); 385 | }); 386 | } 387 | 388 | private addRatios(ratios: RatioType[]) { 389 | this.ratios = []; 390 | ratios.forEach((ratioType: RatioType) => { 391 | const addedRation = NGX_DEFAULT_RATIOS.find((ratio: NgxAspectRatio) => { 392 | return ratio.text === ratioType; 393 | }); 394 | this.ratios.push(addedRation); 395 | }); 396 | } 397 | 398 | public handleCrop() { 399 | 400 | this.loading = true; 401 | setTimeout(() => { 402 | this.croppedImage = this.cropper.getCroppedCanvas({fillColor: this.canvasFillColor}) 403 | .toDataURL(this.state.ImageType); 404 | 405 | setTimeout(() => { 406 | this.imageWidth = this.croppedImg.nativeElement.width; 407 | this.imageHeight = this.croppedImg.nativeElement.height; 408 | }); 409 | this.cropper.getCroppedCanvas({fillColor: this.canvasFillColor}).toBlob((blob: Blob) => { 410 | this.blob = blob; 411 | }); 412 | this.zoomIn = 1; 413 | this.loading = false; 414 | }, 2000); 415 | } 416 | 417 | public undoCrop() { 418 | this.croppedImage = null; 419 | this.blob = null; 420 | setTimeout(() => { 421 | this.initializeCropper(); 422 | }, 100); 423 | 424 | } 425 | 426 | public saveImage() { 427 | this.file.emit(new File([this.blob], this.state.ImageName, {type: this.state.ImageType})); 428 | } 429 | 430 | private initializeCropper() { 431 | this.cropper = new Cropper(this.previewImage.nativeElement, { 432 | zoomOnWheel: true, 433 | viewMode: 0, 434 | center: true, 435 | ready: () => this.loading = false, 436 | dragMode: 'move', 437 | crop: (e: CustomEvent) => { 438 | this.imageHeight = Math.round(e.detail.height); 439 | this.imageWidth = Math.round(e.detail.width); 440 | this.cropBoxWidth = Math.round(this.cropper.getCropBoxData().width); 441 | this.cropBoxHeight = Math.round(this.cropper.getCropBoxData().height); 442 | this.canvasWidth = Math.round(this.cropper.getCanvasData().width); 443 | this.canvasHeight = Math.round(this.cropper.getCanvasData().height); 444 | } 445 | }); 446 | 447 | this.setRatio(this.ratios[0].value); 448 | } 449 | 450 | public setRatio(value: any) { 451 | this.cropper.setAspectRatio(value); 452 | } 453 | 454 | public zoomChange(input: any, zoom?: string) { 455 | if (this.croppedImage) { 456 | if (zoom) { 457 | zoom === 'zoomIn' ? this.zoomIn += 0.1 : this.zoomIn -= 0.1; 458 | } else { 459 | if (input < this.sliderValue) { 460 | this.zoomIn = -Math.abs(input / 100); 461 | } else { 462 | this.zoomIn = Math.abs(input / 100); 463 | } 464 | } 465 | if (this.zoomIn <= 0.1) { 466 | this.zoomIn = 0.1; 467 | } 468 | } else { 469 | if (zoom) { 470 | this.cropper.zoom(input); 471 | this.zoomIn = input; 472 | } else { 473 | if (input < this.sliderValue) { 474 | this.cropper.zoom(-Math.abs(input / 100)); 475 | } else { 476 | this.cropper.zoom(Math.abs(input / 100)); 477 | } 478 | if (input === 0) { 479 | this.cropper.zoom(-1); 480 | } 481 | } 482 | } 483 | 484 | if (!zoom) { 485 | this.sliderValue = input; 486 | } else { 487 | input > 0 ? this.sliderValue += Math.abs(input * 100) : this.sliderValue -= Math.abs(input * 100); 488 | } 489 | 490 | if (this.sliderValue < 0) { 491 | this.sliderValue = 0; 492 | } 493 | } 494 | 495 | public setImageWidth(canvasWidth: number) { 496 | if (canvasWidth) { 497 | this.cropper.setCanvasData({ 498 | left: this.cropper.getCanvasData().left, 499 | top: this.cropper.getCanvasData().top, 500 | width: Math.round(canvasWidth), 501 | height: this.cropper.getCanvasData().height 502 | }); 503 | } 504 | } 505 | 506 | public setImageHeight(canvasHeight: number) { 507 | if (canvasHeight) { 508 | this.cropper.setCanvasData({ 509 | left: this.cropper.getCanvasData().left, 510 | top: this.cropper.getCanvasData().top, 511 | width: this.cropper.getCanvasData().width, 512 | height: Math.round(canvasHeight) 513 | }); 514 | } 515 | } 516 | 517 | public setCropBoxWidth(cropBoxWidth: number) { 518 | if (cropBoxWidth) { 519 | this.cropper.setCropBoxData({ 520 | left: this.cropper.getCropBoxData().left, 521 | top: this.cropper.getCropBoxData().top, 522 | width: Math.round(cropBoxWidth), 523 | height: this.cropper.getCropBoxData().height 524 | }); 525 | } 526 | } 527 | 528 | public setCropBoxHeight(cropBoxHeight: number) { 529 | if (cropBoxHeight) { 530 | this.cropper.setCropBoxData({ 531 | left: this.cropper.getCropBoxData().left, 532 | top: this.cropper.getCropBoxData().top, 533 | width: this.cropper.getCropBoxData().width, 534 | height: Math.round(cropBoxHeight) 535 | }); 536 | } 537 | } 538 | 539 | public centerCanvas() { 540 | const cropBoxLeft = (this.cropper.getContainerData().width - this.cropper.getCropBoxData().width) / 2; 541 | const cropBoxTop = (this.cropper.getContainerData().height - this.cropper.getCropBoxData().height) / 2; 542 | const canvasLeft = (this.cropper.getContainerData().width - this.cropper.getCanvasData().width) / 2; 543 | const canvasTop = (this.cropper.getContainerData().height - this.cropper.getCanvasData().height) / 2; 544 | 545 | this.cropper.setCropBoxData({ 546 | left: cropBoxLeft, 547 | top: cropBoxTop, 548 | width: this.cropper.getCropBoxData().width, 549 | height: this.cropper.getCropBoxData().height 550 | }); 551 | this.cropper.setCanvasData({ 552 | left: canvasLeft, 553 | top: canvasTop, 554 | width: this.cropper.getCanvasData().width, 555 | height: this.cropper.getCanvasData().height 556 | }); 557 | } 558 | 559 | } 560 | 561 | 562 | export interface IEditorOptions { 563 | ImageName: string; 564 | ImageUrl?: string; 565 | ImageType?: string; 566 | File?: File; 567 | AspectRatios?: Array; 568 | } 569 | 570 | export type RatioType = "16:9" | '4:3' | '1:1' | '2:3' | 'Default'; 571 | 572 | export class EditorOptions implements IEditorOptions { 573 | ImageName: string; 574 | ImageUrl?: string; 575 | ImageType?: string; 576 | File?: File; 577 | AspectRatios?: Array; 578 | } 579 | 580 | 581 | export interface NgxAspectRatio { 582 | value: number; 583 | text: RatioType; 584 | } 585 | 586 | 587 | 588 | export const NGX_DEFAULT_RATIOS: Array = [ 589 | { 590 | value: 16 / 9, text: '16:9' 591 | }, 592 | { 593 | value: 4 / 3, text: '4:3' 594 | }, 595 | { 596 | value: 1 / 1, text: '1:1' 597 | }, 598 | { 599 | value: 2 / 3, text: '2:3' 600 | }, 601 | { 602 | value: 0 / 0, text: 'Default' 603 | } 604 | ]; 605 | -------------------------------------------------------------------------------- /src/ngx-image-editor.module.d.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | export * from './ngx-image-editor.component'; 3 | export declare class NgxImageEditorModule { 4 | static forRoot(): ModuleWithProviders; 5 | } 6 | -------------------------------------------------------------------------------- /src/ngx-image-editor.module.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {NgxImageEditorComponent} from './ngx-image-editor.component'; 3 | import {FlexLayoutModule} from "@angular/flex-layout"; 4 | import { 5 | MatAutocompleteModule, 6 | MatButtonModule, MatButtonToggleModule, MatIconModule, MatInputModule, MatMenuModule, MatProgressSpinnerModule, 7 | MatSliderModule,MatDialogModule, 8 | MatTabsModule, MatTooltipModule 9 | } from "@angular/material"; 10 | import {FormsModule, ReactiveFormsModule} from "@angular/forms"; 11 | import {CommonModule} from "@angular/common"; 12 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 13 | 14 | 15 | export * from './ngx-image-editor.component'; 16 | 17 | @NgModule({ 18 | 19 | imports: [ 20 | FormsModule, 21 | BrowserAnimationsModule, 22 | CommonModule, 23 | ReactiveFormsModule, 24 | FlexLayoutModule, 25 | MatButtonModule, 26 | MatIconModule, 27 | MatDialogModule, 28 | MatInputModule, 29 | MatMenuModule, 30 | MatProgressSpinnerModule, 31 | MatTabsModule, 32 | MatTooltipModule, 33 | MatButtonToggleModule, 34 | MatSliderModule, 35 | MatAutocompleteModule 36 | ], 37 | declarations: [ 38 | NgxImageEditorComponent 39 | ], 40 | exports: [NgxImageEditorComponent] 41 | }) 42 | 43 | 44 | export class NgxImageEditorModule { 45 | static forRoot(): ModuleWithProviders { 46 | return { 47 | ngModule: NgxImageEditorModule, 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/public_api.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./ngx-image-editor.module"; 2 | -------------------------------------------------------------------------------- /src/public_api.js: -------------------------------------------------------------------------------- 1 | export * from "./ngx-image-editor.module"; 2 | -------------------------------------------------------------------------------- /src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from "./ngx-image-editor.module"; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "module": "es2015", 5 | "target": "es5", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": ["es2015", "dom"] 14 | }, 15 | "exclude": [ 16 | "node_modules" 17 | ], 18 | "angularCompilerOptions": { 19 | "strictMetadataEmit": true, 20 | "skipTemplateCodegen": true 21 | } 22 | } --------------------------------------------------------------------------------