├── README.md ├── bower.json ├── ng-img-crop.css └── ng-img-crop.js /README.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | So this is my version of a slightly modified https://github.com/alexk111/ngImgCrop 4 | 5 | It also uses some things done by: https://github.com/alexk111/ngImgCrop 6 | 7 | to install this: ```bower install js-ng-img-crop``` 8 | 9 | plunkr here without the aspect ratio part. http://jsfiddle.net/LLjkxjv3/4/ 10 | 11 | However, this repo has the aspect ratio in it. I just don't have the time to make a new plunkr at the moment. 12 | 13 | Below is the old documentation. Most of it still applies. 14 | 15 | HTML 16 | ``` 17 |
18 |
Select an image file:
19 |
20 | 21 |
22 |
Cropped Image:
23 |
24 | 25 | Apply Crop 26 |
27 | ``` 28 | 29 | # ngImgCrop 30 | 31 | Simple Image Crop directive for AngularJS. Enables to crop a circle or a square out of an image. 32 | 33 | ## Screenshots 34 | 35 | ![Circle Crop](https://raw.github.com/alexk111/ngImgCrop/master/screenshots/circle_1.jpg "Circle Crop") 36 | 37 | ![Square Crop](https://raw.github.com/alexk111/ngImgCrop/master/screenshots/square_1.jpg "Square Crop") 38 | 39 | ## Live demo 40 | 41 | [Live demo on JSFiddle](http://jsfiddle.net/alexk111/rw6q9/) 42 | 43 | ## Requirements 44 | 45 | - AngularJS 46 | - Modern Browser supporting 47 | 48 | ## Installing 49 | 50 | ### Download 51 | 52 | You have two options to get the files: 53 | - [Download ngImgCrop](https://github.com/alexk111/ngImgCrop/archive/master.zip) files from GitHub. 54 | - Use Bower to download the files. Just run `bower install ngImgCrop`. 55 | 56 | ### Add files 57 | 58 | Add the scripts to your application. Make sure the `ng-img-crop.js` file is inserted **after** the `angular.js` library: 59 | 60 | ```html 61 | 62 | 63 | 64 | ``` 65 | 66 | ### Add a dependancy 67 | 68 | Add the image crop module as a dependancy to your application module: 69 | 70 | ```js 71 | var myAppModule = angular.module('MyApp', ['ngImgCrop']); 72 | ``` 73 | 74 | ## Usage 75 | 76 | 1. Add the image crop directive `` to the HTML file where you want to use an image crop control. *Note:* a container, you place the directive to, should have some pre-defined size (absolute or relative to its parent). That's required, because the image crop control fits the size of its container. 77 | 2. Bind the directive to a source image property (using **image=""** option). The directive will read the image data from that property and watch for updates. The property can be a url to an image, or a data uri. 78 | 3. Bind the directive to a result image property (using **result-image=""** option). On each update, the directive will put the content of the crop area to that property in the data uri format. 79 | 4. Set up the options that make sense to your application. 80 | 5. Done! 81 | 82 | ## Result image 83 | 84 | The result image will always be a square for the both circle and square area types. It's highly recommended to store the image as a square on your back-end, because this will enable you to easily update your pics later, if you decide to implement some design changes. Showing a square image as a circle on the front-end is not a problem - it is as easy as adding a *border-radius* style for that image in a css. 85 | 86 | ## Example code 87 | 88 | The following code enables to select an image using a file input and crop it. The cropped image data is inserted into img each time the crop area updates. 89 | 90 | ```html 91 | 92 | 93 | 94 | 95 | 96 | 104 | 123 | 124 | 125 |
Select an image file:
126 |
127 | 128 |
129 |
Cropped Image:
130 |
131 | 132 | 133 | ``` 134 | 135 | ## Options 136 | 137 | ```html 138 | 152 | ``` 153 | 154 | ### image 155 | 156 | Assignable angular expression to data-bind to. NgImgCrop gets an image for cropping from it. 157 | 158 | ### result-image 159 | 160 | Assignable angular expression to data-bind to. NgImgCrop puts a data uri of a cropped image into it. 161 | 162 | ### change-on-fly 163 | 164 | *Optional*. By default, to reduce CPU usage, when a user drags/resizes the crop area, the result image is only updated after the user stops dragging/resizing. Set true to always update the result image as the user drags/resizes the crop area. 165 | 166 | ### area-type 167 | 168 | *Optional*. Type of the crop area. Possible values: circle|square. Default: circle. 169 | 170 | ### area-min-size 171 | 172 | *Optional*. Min. width/height of the crop area (in pixels). Default: 80. 173 | 174 | ### result-image-size 175 | 176 | *Optional*. Width/height of the result image (in pixels). Default: 200. 177 | 178 | ### result-image-format 179 | 180 | *Optional*. Format of result image. Possible values include image/jpeg, image/png, and image/webp. Browser support varies. Default: image/png. 181 | 182 | ### result-image-quality 183 | 184 | *Optional*. Quality of result image. Possible values between 0.0 and 1.0 inclusive. Default: browser default. 185 | 186 | ### on-change 187 | 188 | *Optional*. Expression to evaluate upon changing the cropped part of the image. The cropped image data is available as $dataURI. 189 | 190 | ### on-load-begin 191 | 192 | *Optional*. Expression to evaluate when the source image starts loading. 193 | 194 | ### on-load-done 195 | 196 | *Optional*. Expression to evaluate when the source image successfully loaded. 197 | 198 | ### on-load-error 199 | 200 | *Optional*. Expression to evaluate when the source image didn't load. 201 | 202 | 203 | ## License 204 | 205 | See the [LICENSE](https://github.com/alexk111/ngImgCrop/blob/master/LICENSE) file. 206 | 207 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JsImageCrop", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/jonny2779/JsImageCrop", 5 | "authors": [ 6 | "Jonathan ODonnell " 7 | ], 8 | "description": "Crop images with as a square, rectangle or circle and scale them. Based on https://github.com/alexk111/ngImgCrop with some improvements from https://github.com/chirgwin", 9 | "main": "ng-img-crop.js", 10 | "keywords": [ 11 | "angular", 12 | "image", 13 | "crop", 14 | "ngImgCrop", 15 | "javascript" 16 | ], 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /ng-img-crop.css: -------------------------------------------------------------------------------- 1 | img-crop{width:100%;height:100%;display:block;position:relative;overflow:hidden}img-crop canvas{display:block;position:absolute;top:50%;left:50%;outline:0;-webkit-tap-highlight-color:transparent} 2 | -------------------------------------------------------------------------------- /ng-img-crop.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ngImgCrop v0.2.1 3 | * https://github.com/alexk111/ngImgCrop 4 | * 5 | * Copyright (c) 2014 Alex Kaul 6 | * License: MIT 7 | * 8 | * Generated at Monday, August 18th, 2014, 11:08:25 AM 9 | */ 10 | (function () { 11 | 'use strict'; 12 | 13 | var crop = angular.module('ngImgCrop', []); 14 | 15 | crop.factory('cropAreaCircle', ['cropArea', function (CropArea) { 16 | var CropAreaCircle = function () { 17 | CropArea.apply(this, arguments); 18 | 19 | this._boxResizeBaseSize = 20; 20 | this._boxResizeNormalRatio = 0.9; 21 | this._boxResizeHoverRatio = 1.2; 22 | this._iconMoveNormalRatio = 0.9; 23 | this._iconMoveHoverRatio = 1.2; 24 | 25 | this._boxResizeNormalSize = this._boxResizeBaseSize * this._boxResizeNormalRatio; 26 | this._boxResizeHoverSize = this._boxResizeBaseSize * this._boxResizeHoverRatio; 27 | 28 | this._posDragStartX = 0; 29 | this._posDragStartY = 0; 30 | this._posResizeStartX = 0; 31 | this._posResizeStartY = 0; 32 | this._posResizeStartSize = {w: 0, h: 0}; 33 | 34 | this._boxResizeIsHover = false; 35 | this._areaIsHover = false; 36 | this._boxResizeIsDragging = false; 37 | this._areaIsDragging = false; 38 | }; 39 | 40 | CropAreaCircle.prototype = new CropArea(); 41 | 42 | // return a type string 43 | CropAreaCircle.prototype.getType = function () { 44 | return 'circle'; 45 | } 46 | 47 | CropAreaCircle.prototype._calcCirclePerimeterCoords = function (angleDegrees) { 48 | var c = this.getCenterPoint(); 49 | var s = this.getSize(); 50 | var angleRadians = angleDegrees * (Math.PI / 180), 51 | circlePerimeterX = c.x + s.w / 2 * Math.cos(angleRadians), 52 | circlePerimeterY = c.y + s.h / 2 * Math.sin(angleRadians); 53 | return [circlePerimeterX, circlePerimeterY]; 54 | }; 55 | 56 | CropAreaCircle.prototype._calcResizeIconCenterCoords = function () { 57 | return this._calcCirclePerimeterCoords(-45); 58 | }; 59 | 60 | CropAreaCircle.prototype._isCoordWithinArea = function (coord) { 61 | var c = this.getCenterPoint(); 62 | return Math.sqrt((coord[0] - c.x) * (coord[0] - c.x) + (coord[1] - c.y) * (coord[1] - c.y)) < this._size.h / 2; 63 | }; 64 | CropAreaCircle.prototype._isCoordWithinBoxResize = function (coord) { 65 | var resizeIconCenterCoords = this._calcResizeIconCenterCoords(); 66 | var hSize = this._boxResizeHoverSize / 2; 67 | return (coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize && 68 | coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize); 69 | }; 70 | 71 | CropAreaCircle.prototype._drawArea = function (ctx, center, size) { 72 | ctx.arc(center.x, center.y, size.h / 2, 0, 2 * Math.PI); 73 | }; 74 | 75 | CropAreaCircle.prototype.draw = function () { 76 | CropArea.prototype.draw.apply(this, arguments); 77 | 78 | // draw move icon 79 | var c = this.getCenterPoint(); 80 | this._cropCanvas.drawIconMove([c.x, c.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); 81 | 82 | // draw resize cubes 83 | this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover ? this._boxResizeHoverRatio : this._boxResizeNormalRatio); 84 | }; 85 | 86 | CropAreaCircle.prototype.processMouseMove = function (mouseCurX, mouseCurY) { 87 | var cursor = 'default'; 88 | var res = false; 89 | 90 | this._boxResizeIsHover = false; 91 | this._areaIsHover = false; 92 | 93 | if (this._areaIsDragging) { 94 | this.setCenterPoint({ 95 | x: mouseCurX - this._posDragStartX, 96 | y: mouseCurY - this._posDragStartY 97 | }); 98 | this._areaIsHover = true; 99 | cursor = 'move'; 100 | res = true; 101 | this._events.trigger('area-move'); 102 | } else if (this._boxResizeIsDragging) { 103 | cursor = 'nesw-resize'; 104 | var iFR, iFX, iFY; 105 | iFX = mouseCurX - this._posResizeStartX; 106 | iFY = this._posResizeStartY - mouseCurY; 107 | if (iFX > iFY) { 108 | iFR = this._posResizeStartSize.h + iFY * 2; 109 | } else { 110 | iFR = this._posResizeStartSize.w + iFX * 2; 111 | } 112 | 113 | var prevCenter = this.getCenterPoint(); 114 | 115 | this.setSize(Math.max(this._minSize.h, iFR)); 116 | 117 | //recenter 118 | this.setCenterPoint(prevCenter); 119 | 120 | this._boxResizeIsHover = true; 121 | res = true; 122 | this._events.trigger('area-resize'); 123 | } else if (this._isCoordWithinBoxResize([mouseCurX, mouseCurY])) { 124 | cursor = 'nesw-resize'; 125 | this._areaIsHover = false; 126 | this._boxResizeIsHover = true; 127 | res = true; 128 | } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 129 | cursor = 'move'; 130 | this._areaIsHover = true; 131 | res = true; 132 | } 133 | 134 | angular.element(this._ctx.canvas).css({'cursor': cursor}); 135 | 136 | return res; 137 | }; 138 | 139 | CropAreaCircle.prototype.processMouseDown = function (mouseDownX, mouseDownY) { 140 | if (this._isCoordWithinBoxResize([mouseDownX, mouseDownY])) { 141 | this._areaIsDragging = false; 142 | this._areaIsHover = false; 143 | this._boxResizeIsDragging = true; 144 | this._boxResizeIsHover = true; 145 | this._posResizeStartX = mouseDownX; 146 | this._posResizeStartY = mouseDownY; 147 | this._posResizeStartSize = this._size; 148 | this._events.trigger('area-resize-start'); 149 | } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { 150 | this._areaIsDragging = true; 151 | this._areaIsHover = true; 152 | this._boxResizeIsDragging = false; 153 | this._boxResizeIsHover = false; 154 | this._posDragStartX = mouseDownX - this.getCenterPoint().x; 155 | this._posDragStartY = mouseDownY - this.getCenterPoint().y; 156 | this._events.trigger('area-move-start'); 157 | } 158 | }; 159 | 160 | CropAreaCircle.prototype.processMouseUp = function (/*mouseUpX, mouseUpY*/) { 161 | if (this._areaIsDragging) { 162 | this._areaIsDragging = false; 163 | this._events.trigger('area-move-end'); 164 | } 165 | if (this._boxResizeIsDragging) { 166 | this._boxResizeIsDragging = false; 167 | this._events.trigger('area-resize-end'); 168 | } 169 | this._areaIsHover = false; 170 | this._boxResizeIsHover = false; 171 | 172 | this._posDragStartX = 0; 173 | this._posDragStartY = 0; 174 | }; 175 | 176 | return CropAreaCircle; 177 | }]); 178 | 179 | 180 | crop.factory('cropAreaRectangle', ['cropArea', function (CropArea) { 181 | var CropAreaRectangle = function () { 182 | CropArea.apply(this, arguments); 183 | 184 | this._resizeCtrlBaseRadius = 10; 185 | this._resizeCtrlNormalRatio = 0.75; 186 | this._resizeCtrlHoverRatio = 1; 187 | this._iconMoveNormalRatio = 0.9; 188 | this._iconMoveHoverRatio = 1.2; 189 | 190 | this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio; 191 | this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio; 192 | 193 | this._posDragStartX = 0; 194 | this._posDragStartY = 0; 195 | this._posResizeStartX = 0; 196 | this._posResizeStartY = 0; 197 | this._posResizeStartSize = {w: 0, h: 0}; 198 | 199 | this._resizeCtrlIsHover = -1; 200 | this._areaIsHover = false; 201 | this._resizeCtrlIsDragging = -1; 202 | this._areaIsDragging = false; 203 | }; 204 | 205 | CropAreaRectangle.prototype = new CropArea(); 206 | 207 | // return a type string 208 | CropAreaRectangle.prototype.getType = function () { 209 | return 'rectangle'; 210 | } 211 | 212 | CropAreaRectangle.prototype._calcRectangleCorners = function () { 213 | var size = this.getSize(); 214 | var se = this.getSouthEastBound(); 215 | return [ 216 | [size.x, size.y], //northwest 217 | [se.x, size.y], //northeast 218 | [size.x, se.y], //southwest 219 | [se.x, se.y] //southeast 220 | ]; 221 | }; 222 | 223 | CropAreaRectangle.prototype._calcRectangleDimensions = function () { 224 | var size = this.getSize(); 225 | var se = this.getSouthEastBound(); 226 | return { 227 | left: size.x, 228 | top: size.y, 229 | right: se.x, 230 | bottom: se.y 231 | }; 232 | }; 233 | 234 | CropAreaRectangle.prototype._isCoordWithinArea = function (coord) { 235 | var rectangleDimensions = this._calcRectangleDimensions(); 236 | return (coord[0] >= rectangleDimensions.left && coord[0] <= rectangleDimensions.right && coord[1] >= rectangleDimensions.top && coord[1] <= rectangleDimensions.bottom); 237 | }; 238 | 239 | CropAreaRectangle.prototype._isCoordWithinResizeCtrl = function (coord) { 240 | var resizeIconsCenterCoords = this._calcRectangleCorners(); 241 | var res = -1; 242 | for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 243 | var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 244 | if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius && 245 | coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) { 246 | res = i; 247 | break; 248 | } 249 | } 250 | return res; 251 | }; 252 | 253 | CropAreaRectangle.prototype._drawArea = function (ctx, center, size) { 254 | ctx.rect(size.x, size.y, size.w, size.h); 255 | }; 256 | 257 | CropAreaRectangle.prototype.draw = function () { 258 | CropArea.prototype.draw.apply(this, arguments); 259 | 260 | var center = this.getCenterPoint(); 261 | // draw move icon 262 | this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); 263 | 264 | // draw resize thumbs 265 | var resizeIconsCenterCoords = this._calcRectangleCorners(); 266 | for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 267 | var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 268 | this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio); 269 | } 270 | }; 271 | 272 | CropAreaRectangle.prototype.processMouseMove = function (mouseCurX, mouseCurY) { 273 | var cursor = 'default'; 274 | var res = false; 275 | 276 | this._resizeCtrlIsHover = -1; 277 | this._areaIsHover = false; 278 | 279 | if (this._areaIsDragging) { 280 | this.setCenterPoint({ 281 | x: mouseCurX - this._posDragStartX, 282 | y: mouseCurY - this._posDragStartY 283 | }); 284 | this._areaIsHover = true; 285 | cursor = 'move'; 286 | res = true; 287 | this._events.trigger('area-move'); 288 | } else if (this._resizeCtrlIsDragging > -1) { 289 | var s = this.getSize(); 290 | var se = this.getSouthEastBound(); 291 | switch (this._resizeCtrlIsDragging) { 292 | case 0: // Top Left 293 | this.setSizeByCorners({x: mouseCurX, y: mouseCurY}, { 294 | x: se.x, 295 | y: se.y 296 | }); 297 | cursor = 'nwse-resize'; 298 | break; 299 | case 1: // Top Right 300 | this.setSizeByCorners({x: s.x, y: mouseCurY}, { 301 | x: mouseCurX, 302 | y: se.y 303 | }); 304 | cursor = 'nesw-resize'; 305 | break; 306 | case 2: // Bottom Left 307 | this.setSizeByCorners({x: mouseCurX, y: s.y}, { 308 | x: se.x, 309 | y: mouseCurY 310 | }); 311 | cursor = 'nesw-resize'; 312 | break; 313 | case 3: // Bottom Right 314 | this.setSizeByCorners({x: s.x, y: s.y}, { 315 | x: mouseCurX, 316 | y: mouseCurY 317 | }); 318 | cursor = 'nwse-resize'; 319 | break; 320 | } 321 | 322 | this._resizeCtrlIsHover = this._resizeCtrlIsDragging; 323 | res = true; 324 | this._events.trigger('area-resize'); 325 | } else { 326 | var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]); 327 | if (hoveredResizeBox > -1) { 328 | switch (hoveredResizeBox) { 329 | case 0: 330 | cursor = 'nwse-resize'; 331 | break; 332 | case 1: 333 | cursor = 'nesw-resize'; 334 | break; 335 | case 2: 336 | cursor = 'nesw-resize'; 337 | break; 338 | case 3: 339 | cursor = 'nwse-resize'; 340 | break; 341 | } 342 | this._areaIsHover = false; 343 | this._resizeCtrlIsHover = hoveredResizeBox; 344 | res = true; 345 | } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 346 | cursor = 'move'; 347 | this._areaIsHover = true; 348 | res = true; 349 | } 350 | } 351 | 352 | angular.element(this._ctx.canvas).css({'cursor': cursor}); 353 | 354 | return res; 355 | }; 356 | 357 | CropAreaRectangle.prototype.processMouseDown = function (mouseDownX, mouseDownY) { 358 | var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]); 359 | if (isWithinResizeCtrl > -1) { 360 | this._areaIsDragging = false; 361 | this._areaIsHover = false; 362 | this._resizeCtrlIsDragging = isWithinResizeCtrl; 363 | this._resizeCtrlIsHover = isWithinResizeCtrl; 364 | this._posResizeStartX = mouseDownX; 365 | this._posResizeStartY = mouseDownY; 366 | this._posResizeStartSize = this._size; 367 | this._events.trigger('area-resize-start'); 368 | } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { 369 | this._areaIsDragging = true; 370 | this._areaIsHover = true; 371 | this._resizeCtrlIsDragging = -1; 372 | this._resizeCtrlIsHover = -1; 373 | var center = this.getCenterPoint(); 374 | this._posDragStartX = mouseDownX - center.x; 375 | this._posDragStartY = mouseDownY - center.y; 376 | this._events.trigger('area-move-start'); 377 | } 378 | }; 379 | 380 | CropAreaRectangle.prototype.processMouseUp = function (/*mouseUpX, mouseUpY*/) { 381 | if (this._areaIsDragging) { 382 | this._areaIsDragging = false; 383 | this._events.trigger('area-move-end'); 384 | } 385 | if (this._resizeCtrlIsDragging > -1) { 386 | this._resizeCtrlIsDragging = -1; 387 | this._events.trigger('area-resize-end'); 388 | } 389 | this._areaIsHover = false; 390 | this._resizeCtrlIsHover = -1; 391 | 392 | this._posDragStartX = 0; 393 | this._posDragStartY = 0; 394 | }; 395 | 396 | return CropAreaRectangle; 397 | }]); 398 | 399 | 400 | crop.factory('cropAreaSquare', ['cropArea', 'cropAreaRectangle', function (CropArea, CropAreaRectangle) { 401 | var CropAreaSquare = function () { 402 | CropAreaRectangle.apply(this, arguments); 403 | }; 404 | 405 | CropAreaSquare.prototype = new CropAreaRectangle(); 406 | 407 | // return a type string 408 | CropAreaSquare.prototype.getType = function () { 409 | return 'square'; 410 | } 411 | 412 | // override rectangle's mouse move method 413 | CropAreaSquare.prototype.processMouseMove = function (mouseCurX, mouseCurY) { 414 | var cursor = 'default'; 415 | var res = false; 416 | 417 | this._resizeCtrlIsHover = -1; 418 | this._areaIsHover = false; 419 | 420 | if (this._areaIsDragging) { 421 | this.setCenterPoint({ 422 | x: mouseCurX - this._posDragStartX, 423 | y: mouseCurY - this._posDragStartY 424 | }); 425 | this._areaIsHover = true; 426 | cursor = 'move'; 427 | res = true; 428 | this._events.trigger('area-move'); 429 | } else if (this._resizeCtrlIsDragging > -1) { 430 | var xMulti, yMulti; 431 | switch (this._resizeCtrlIsDragging) { 432 | case 0: // Top Left 433 | xMulti = -1; 434 | yMulti = -1; 435 | cursor = 'nwse-resize'; 436 | break; 437 | case 1: // Top Right 438 | xMulti = 1; 439 | yMulti = -1; 440 | cursor = 'nesw-resize'; 441 | break; 442 | case 2: // Bottom Left 443 | xMulti = -1; 444 | yMulti = 1; 445 | cursor = 'nesw-resize'; 446 | break; 447 | case 3: // Bottom Right 448 | xMulti = 1; 449 | yMulti = 1; 450 | cursor = 'nwse-resize'; 451 | break; 452 | } 453 | var iFX = (mouseCurX - this._posResizeStartX) * xMulti; 454 | var iFY = (mouseCurY - this._posResizeStartY) * yMulti; 455 | var iFR; 456 | if (iFX > iFY) { 457 | iFR = this._posResizeStartSize.w + iFY; 458 | } else { 459 | iFR = this._posResizeStartSize.h + iFX; 460 | } 461 | var prevCenter = this.getCenterPoint(); 462 | 463 | this.setSize(Math.max(this._minSize.w, iFR)); 464 | 465 | //recenter 466 | this.setCenterPoint(prevCenter); 467 | 468 | this._resizeCtrlIsHover = this._resizeCtrlIsDragging; 469 | res = true; 470 | this._events.trigger('area-resize'); 471 | } else { 472 | var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]); 473 | if (hoveredResizeBox > -1) { 474 | switch (hoveredResizeBox) { 475 | case 0: 476 | cursor = 'nwse-resize'; 477 | break; 478 | case 1: 479 | cursor = 'nesw-resize'; 480 | break; 481 | case 2: 482 | cursor = 'nesw-resize'; 483 | break; 484 | case 3: 485 | cursor = 'nwse-resize'; 486 | break; 487 | } 488 | this._areaIsHover = false; 489 | this._resizeCtrlIsHover = hoveredResizeBox; 490 | res = true; 491 | } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 492 | cursor = 'move'; 493 | this._areaIsHover = true; 494 | res = true; 495 | } 496 | } 497 | 498 | angular.element(this._ctx.canvas).css({'cursor': cursor}); 499 | 500 | return res; 501 | }; 502 | 503 | return CropAreaSquare; 504 | }]); 505 | 506 | 507 | crop.factory('cropArea', ['cropCanvas', function (CropCanvas) { 508 | var CropArea = function (ctx, events) { 509 | this._ctx = ctx; 510 | this._events = events; 511 | 512 | this._minSize = {x: 0, y: 0, w: 80, h: 80}; 513 | 514 | this._cropCanvas = new CropCanvas(ctx); 515 | 516 | this._image = new Image(); 517 | this._size = {x: 0, y: 0, w: 200, h: 200}; 518 | }; 519 | 520 | /* GETTERS/SETTERS */ 521 | 522 | CropArea.prototype.getImage = function () { 523 | return this._image; 524 | }; 525 | CropArea.prototype.setImage = function (image) { 526 | this._image = image; 527 | }; 528 | 529 | CropArea.prototype.getSize = function () { 530 | return this._size; 531 | }; 532 | 533 | CropArea.prototype.setSize = function (size) { 534 | 535 | size = this._processSize(size); 536 | this._size = this._preventBoundaryCollision(size); 537 | }; 538 | 539 | CropArea.prototype.setSizeByCorners = function (northWestCorner, southEastCorner) { 540 | 541 | var size = { 542 | x: northWestCorner.x, 543 | y: northWestCorner.y, 544 | w: southEastCorner.x - northWestCorner.x, 545 | h: southEastCorner.y - northWestCorner.y 546 | }; 547 | this.setSize(size); 548 | }; 549 | 550 | CropArea.prototype.getSouthEastBound = function () { 551 | return this._southEastBound(this.getSize()); 552 | }; 553 | 554 | CropArea.prototype.getMinSize = function () { 555 | return this._minSize; 556 | }; 557 | 558 | CropArea.prototype.getCenterPoint = function () { 559 | var s = this.getSize(); 560 | return { 561 | x: s.x + (s.w / 2), 562 | y: s.y + (s.h / 2) 563 | }; 564 | }; 565 | 566 | CropArea.prototype.setCenterPoint = function (point) { 567 | var s = this.getSize(); 568 | this.setSize({x: point.x - s.w / 2, y: point.y - s.h / 2, w: s.w, h: s.h}); 569 | }; 570 | 571 | CropArea.prototype.setMinSize = function (size) { 572 | this._minSize = this._processSize(size); 573 | this.setSize(this._minSize); 574 | }; 575 | 576 | // return a type string 577 | CropArea.prototype.getType = function () { 578 | //default to circle 579 | return 'circle'; 580 | } 581 | 582 | /* FUNCTIONS */ 583 | CropArea.prototype._preventBoundaryCollision = function (size) { 584 | var canvasH = this._ctx.canvas.height, 585 | canvasW = this._ctx.canvas.width; 586 | 587 | var nw = {x: size.x, y: size.y}; 588 | var se = this._southEastBound(size); 589 | 590 | // check northwest corner 591 | if (nw.x < 0) { 592 | nw.x = 0; 593 | } 594 | if (nw.y < 0) { 595 | nw.y = 0; 596 | } 597 | 598 | // check southeast corner 599 | if (se.x > canvasW) { 600 | se.x = canvasW 601 | } 602 | if (se.y > canvasH) { 603 | se.y = canvasH 604 | } 605 | 606 | var newSize = { 607 | x: nw.x, 608 | y: nw.y, 609 | w: se.x - nw.x, 610 | h: se.y - nw.y 611 | }; 612 | 613 | //check size (if < min, adjust nw corner) 614 | if (newSize.w < this._minSize.w) { 615 | newSize.w = this._minSize.w; 616 | se = this._southEastBound(newSize); 617 | //adjust se corner, if it's out of bounds 618 | if (se.x > canvasW) { 619 | se.x = canvasW; 620 | //adjust nw corner according to min width 621 | nw.x = Math.max(se.x - canvasW, se.x - this._minSize.w); 622 | newSize = { 623 | x: nw.x, 624 | y: nw.y, 625 | w: se.x - nw.x, 626 | h: se.y - nw.y 627 | }; 628 | } 629 | } 630 | 631 | if (newSize.h < this._minSize.h) { 632 | newSize.h = this._minSize.h; 633 | se = this._southEastBound(newSize); 634 | 635 | if (se.y > canvasH) { 636 | se.y = canvasH; 637 | //adjust nw corner according to min height 638 | nw.y = Math.max(se.y - canvasH, se.y - this._minSize.h); 639 | newSize = { 640 | x: nw.x, 641 | y: nw.y, 642 | w: se.x - nw.x, 643 | h: se.y - nw.y 644 | }; 645 | } 646 | } 647 | 648 | //finally, enforce 1:1 aspect ratio for sqaure-like selections 649 | if (this.getType() === "circle" || this.getType() === "square") { 650 | newSize = { 651 | x: newSize.x, 652 | y: newSize.y, 653 | w: newSize.w, 654 | h: newSize.h 655 | }; 656 | } 657 | return newSize; 658 | }; 659 | 660 | CropArea.prototype._drawArea = function () { 661 | }; 662 | 663 | CropArea.prototype._processSize = function (size) { 664 | // make this polymorphic to accept a single floating point number 665 | // for square-like sizes (including circle) 666 | if (typeof size == "number") { 667 | size = {w: size, h: size}; 668 | } 669 | 670 | return { 671 | x: size.x || this._minSize.x, 672 | y: size.y || this._minSize.y, 673 | w: size.w || this._minSize.w, 674 | h: size.h || this._minSize.h 675 | }; 676 | } 677 | 678 | CropArea.prototype._southEastBound = function (size) { 679 | return {x: size.x + size.w, y: size.y + size.h}; 680 | } 681 | CropArea.prototype.draw = function () { 682 | // draw crop area 683 | this._cropCanvas.drawCropArea(this._image, this.getCenterPoint(), this._size, this._drawArea); 684 | }; 685 | 686 | CropArea.prototype.processMouseMove = function () { 687 | }; 688 | 689 | CropArea.prototype.processMouseDown = function () { 690 | }; 691 | 692 | CropArea.prototype.processMouseUp = function () { 693 | }; 694 | 695 | return CropArea; 696 | }]); 697 | 698 | 699 | crop.factory('cropCanvas', [function () { 700 | // Shape = Array of [x,y]; [0, 0] - center 701 | var shapeArrowNW = [[-0.5, -2], [-3, -4.5], [-0.5, -7], [-7, -7], [-7, -0.5], [-4.5, -3], [-2, -0.5]]; 702 | var shapeArrowNE = [[0.5, -2], [3, -4.5], [0.5, -7], [7, -7], [7, -0.5], [4.5, -3], [2, -0.5]]; 703 | var shapeArrowSW = [[-0.5, 2], [-3, 4.5], [-0.5, 7], [-7, 7], [-7, 0.5], [-4.5, 3], [-2, 0.5]]; 704 | var shapeArrowSE = [[0.5, 2], [3, 4.5], [0.5, 7], [7, 7], [7, 0.5], [4.5, 3], [2, 0.5]]; 705 | var shapeArrowN = [[-1.5, -2.5], [-1.5, -6], [-5, -6], [0, -11], [5, -6], [1.5, -6], [1.5, -2.5]]; 706 | var shapeArrowW = [[-2.5, -1.5], [-6, -1.5], [-6, -5], [-11, 0], [-6, 5], [-6, 1.5], [-2.5, 1.5]]; 707 | var shapeArrowS = [[-1.5, 2.5], [-1.5, 6], [-5, 6], [0, 11], [5, 6], [1.5, 6], [1.5, 2.5]]; 708 | var shapeArrowE = [[2.5, -1.5], [6, -1.5], [6, -5], [11, 0], [6, 5], [6, 1.5], [2.5, 1.5]]; 709 | 710 | // Colors 711 | var colors = { 712 | areaOutline: '#fff', 713 | resizeBoxStroke: '#fff', 714 | resizeBoxFill: '#444', 715 | resizeBoxArrowFill: '#fff', 716 | resizeCircleStroke: '#fff', 717 | resizeCircleFill: '#444', 718 | moveIconFill: '#fff' 719 | }; 720 | 721 | return function (ctx) { 722 | 723 | /* Base functions */ 724 | 725 | // Calculate Point 726 | var calcPoint = function (point, offset, scale) { 727 | return [scale * point[0] + offset[0], scale * point[1] + offset[1]]; 728 | }; 729 | 730 | // Draw Filled Polygon 731 | var drawFilledPolygon = function (shape, fillStyle, centerCoords, scale) { 732 | ctx.save(); 733 | ctx.fillStyle = fillStyle; 734 | ctx.beginPath(); 735 | var pc, pc0 = calcPoint(shape[0], centerCoords, scale); 736 | ctx.moveTo(pc0[0], pc0[1]); 737 | 738 | for (var p in shape) { 739 | if (p > 0) { 740 | pc = calcPoint(shape[p], centerCoords, scale); 741 | ctx.lineTo(pc[0], pc[1]); 742 | } 743 | } 744 | 745 | ctx.lineTo(pc0[0], pc0[1]); 746 | ctx.fill(); 747 | ctx.closePath(); 748 | ctx.restore(); 749 | }; 750 | 751 | /* Icons */ 752 | 753 | this.drawIconMove = function (centerCoords, scale) { 754 | drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale); 755 | drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale); 756 | drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale); 757 | drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale); 758 | }; 759 | 760 | this.drawIconResizeCircle = function (centerCoords, circleRadius, scale) { 761 | var scaledCircleRadius = circleRadius * scale; 762 | ctx.save(); 763 | ctx.strokeStyle = colors.resizeCircleStroke; 764 | ctx.lineWidth = 2; 765 | ctx.fillStyle = colors.resizeCircleFill; 766 | ctx.beginPath(); 767 | ctx.arc(centerCoords[0], centerCoords[1], scaledCircleRadius, 0, 2 * Math.PI); 768 | ctx.fill(); 769 | ctx.stroke(); 770 | ctx.closePath(); 771 | ctx.restore(); 772 | }; 773 | 774 | this.drawIconResizeBoxBase = function (centerCoords, boxSize, scale) { 775 | var scaledBoxSize = boxSize * scale; 776 | ctx.save(); 777 | ctx.strokeStyle = colors.resizeBoxStroke; 778 | ctx.lineWidth = 2; 779 | ctx.fillStyle = colors.resizeBoxFill; 780 | ctx.fillRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); 781 | ctx.strokeRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); 782 | ctx.restore(); 783 | }; 784 | this.drawIconResizeBoxNESW = function (centerCoords, boxSize, scale) { 785 | this.drawIconResizeBoxBase(centerCoords, boxSize, scale); 786 | drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale); 787 | drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale); 788 | }; 789 | this.drawIconResizeBoxNWSE = function (centerCoords, boxSize, scale) { 790 | this.drawIconResizeBoxBase(centerCoords, boxSize, scale); 791 | drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale); 792 | drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale); 793 | }; 794 | 795 | /* Crop Area */ 796 | 797 | this.drawCropArea = function (image, center, size, fnDrawClipPath) { 798 | var xRatio = image.width / ctx.canvas.width, 799 | yRatio = image.height / ctx.canvas.height, 800 | xLeft = size.x, 801 | yTop = size.y; 802 | 803 | ctx.save(); 804 | ctx.strokeStyle = colors.areaOutline; 805 | ctx.lineWidth = 2; 806 | ctx.beginPath(); 807 | fnDrawClipPath(ctx, center, size); 808 | ctx.stroke(); 809 | ctx.clip(); 810 | 811 | // draw part of original image 812 | if (size.w > 0 && size.w > 0) { 813 | ctx.drawImage(image, xLeft * xRatio, yTop * yRatio, size.w * xRatio, size.h * yRatio, xLeft, yTop, size.w, size.h); 814 | } 815 | 816 | ctx.beginPath(); 817 | fnDrawClipPath(ctx, center, size); 818 | ctx.stroke(); 819 | ctx.clip(); 820 | 821 | ctx.restore(); 822 | }; 823 | 824 | }; 825 | }]); 826 | 827 | 828 | crop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropAreaRectangle', function ($document, CropAreaCircle, CropAreaSquare, CropAreaRectangle) { 829 | /* STATIC FUNCTIONS */ 830 | 831 | // Get Element's Offset 832 | var getElementOffset = function (elem) { 833 | var box = elem.getBoundingClientRect(); 834 | 835 | var body = document.body; 836 | var docElem = document.documentElement; 837 | 838 | var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; 839 | var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; 840 | 841 | var clientTop = docElem.clientTop || body.clientTop || 0; 842 | var clientLeft = docElem.clientLeft || body.clientLeft || 0; 843 | 844 | var top = box.top + scrollTop - clientTop; 845 | var left = box.left + scrollLeft - clientLeft; 846 | 847 | return {top: Math.round(top), left: Math.round(left)}; 848 | }; 849 | 850 | return function (elCanvas, opts, events) { 851 | /* PRIVATE VARIABLES */ 852 | 853 | // Object Pointers 854 | var ctx = null, 855 | image = null, 856 | theArea = null, 857 | self = this; 858 | 859 | // Dimensions 860 | var minCanvasDims = [100, 100], 861 | maxCanvasDims = [300, 300]; 862 | 863 | // Result Image size 864 | var resImgSize = {w: 200, h: 200}; 865 | 866 | /* PRIVATE FUNCTIONS */ 867 | 868 | // Draw Scene 869 | function drawScene() { 870 | // clear canvas 871 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 872 | 873 | if (image !== null) { 874 | // draw source image 875 | ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); 876 | 877 | ctx.save(); 878 | 879 | // and make it darker 880 | ctx.fillStyle = 'rgba(0, 0, 0, 0.65)'; 881 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 882 | 883 | ctx.restore(); 884 | 885 | // draw Area 886 | theArea.draw(); 887 | } 888 | } 889 | 890 | // Resets CropHost 891 | var resetCropHost = function () { 892 | if (image !== null) { 893 | theArea.setImage(image); 894 | var imageDims = [image.width, image.height], 895 | imageRatio = image.width / image.height, 896 | canvasDims = imageDims; 897 | 898 | if (canvasDims[0] > maxCanvasDims[0]) { 899 | canvasDims[0] = maxCanvasDims[0]; 900 | canvasDims[1] = canvasDims[0] / imageRatio; 901 | } else if (canvasDims[0] < minCanvasDims[0]) { 902 | canvasDims[0] = minCanvasDims[0]; 903 | canvasDims[1] = canvasDims[0] / imageRatio; 904 | } 905 | if (canvasDims[1] > maxCanvasDims[1]) { 906 | canvasDims[1] = maxCanvasDims[1]; 907 | canvasDims[0] = canvasDims[1] * imageRatio; 908 | } else if (canvasDims[1] < minCanvasDims[1]) { 909 | canvasDims[1] = minCanvasDims[1]; 910 | canvasDims[0] = canvasDims[1] * imageRatio; 911 | } 912 | elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]).css({ 913 | 'margin-left': -canvasDims[0] / 2 + 'px', 914 | 'margin-top': -canvasDims[1] / 2 + 'px' 915 | }); 916 | 917 | var cw = ctx.canvas.width; 918 | var ch = ctx.canvas.height; 919 | 920 | var areaType = self.getAreaType(); 921 | // enforce 1:1 aspect ratio for square-like selections 922 | if ((areaType === 'circle') || (areaType === 'square')) { 923 | 924 | } 925 | 926 | theArea.setSize({ 927 | w: Math.min(200, cw / 2), 928 | h: Math.min(200, ch / 2) 929 | }); 930 | //TODO: set top left corner point 931 | theArea.setCenterPoint({ 932 | x: ctx.canvas.width / 2, 933 | y: ctx.canvas.height / 2 934 | }); 935 | 936 | } else { 937 | elCanvas.prop('width', 0).prop('height', 0).css({'margin-top': 0}); 938 | } 939 | 940 | drawScene(); 941 | }; 942 | 943 | var onMouseMove = function (e) { 944 | if (image !== null) { 945 | var offset = getElementOffset(ctx.canvas), 946 | pageX, pageY; 947 | if (e.type === 'touchmove') { 948 | pageX = e.changedTouches[0].pageX; 949 | pageY = e.changedTouches[0].pageY; 950 | } else { 951 | pageX = e.pageX; 952 | pageY = e.pageY; 953 | } 954 | theArea.processMouseMove(pageX - offset.left, pageY - offset.top); 955 | drawScene(); 956 | } 957 | }; 958 | 959 | var onMouseDown = function (e) { 960 | e.preventDefault(); 961 | e.stopPropagation(); 962 | if (image !== null) { 963 | var offset = getElementOffset(ctx.canvas), 964 | pageX, pageY; 965 | if (e.type === 'touchstart') { 966 | pageX = e.changedTouches[0].pageX; 967 | pageY = e.changedTouches[0].pageY; 968 | } else { 969 | pageX = e.pageX; 970 | pageY = e.pageY; 971 | } 972 | theArea.processMouseDown(pageX - offset.left, pageY - offset.top); 973 | drawScene(); 974 | } 975 | }; 976 | 977 | var onMouseUp = function (e) { 978 | if (image !== null) { 979 | var offset = getElementOffset(ctx.canvas), 980 | pageX, pageY; 981 | if (e.type === 'touchend') { 982 | pageX = e.changedTouches[0].pageX; 983 | pageY = e.changedTouches[0].pageY; 984 | } else { 985 | pageX = e.pageX; 986 | pageY = e.pageY; 987 | } 988 | theArea.processMouseUp(pageX - offset.left, pageY - offset.top); 989 | drawScene(); 990 | } 991 | }; 992 | 993 | this.getResultImage = function () { 994 | var temp_ctx, temp_canvas; 995 | temp_canvas = angular.element('')[0]; 996 | temp_ctx = temp_canvas.getContext('2d'); 997 | var ris = this.getResultImageSize(); 998 | // TODO: pull request 999 | if (image != null) { 1000 | var adjusted_width = image.width *theArea.getSize().w/ctx.canvas.width; 1001 | var adjusted_height = image.height*theArea.getSize().h/ctx.canvas.height; 1002 | temp_canvas.width = adjusted_width; 1003 | temp_canvas.height = adjusted_height; 1004 | var center = theArea.getCenterPoint(); 1005 | var retObj = { 1006 | dataURI: null, 1007 | imageData: null 1008 | }; 1009 | 1010 | 1011 | //console.log(temp_canvas); 1012 | //console.log(ris); 1013 | //console.log(theArea.getSize()); 1014 | retObj.size = theArea.getSize(); 1015 | temp_ctx.drawImage(image, 1016 | (center.x - theArea.getSize().w / 2) * (image.width / ctx.canvas.width), 1017 | (center.y - theArea.getSize().h / 2) * (image.height / ctx.canvas.height), 1018 | theArea.getSize().w * (image.width / ctx.canvas.width), 1019 | theArea.getSize().h * (image.height / ctx.canvas.height), 1020 | 0, 1021 | 0, 1022 | adjusted_width, 1023 | adjusted_height); 1024 | //temp_ctx.drawImage(image, 0, 0, adjusted_width, adjusted_height); 1025 | retObj.dataURI = temp_canvas.toDataURL(); 1026 | retObj.imageData = temp_canvas.getContext("2d").getImageData(0, 0, adjusted_width, adjusted_height); 1027 | } 1028 | return retObj; 1029 | }; 1030 | 1031 | this.getAreaCoords = function () { 1032 | return theArea.getSize() 1033 | } 1034 | 1035 | this.setNewImageSource = function (imageSource) { 1036 | image = null; 1037 | resetCropHost(); 1038 | events.trigger('image-updated'); 1039 | if (!!imageSource) { 1040 | var newImage = new Image(); 1041 | newImage.onload = function () { 1042 | events.trigger('load-done'); 1043 | image = newImage; 1044 | resetCropHost(); 1045 | events.trigger('image-updated'); 1046 | }; 1047 | newImage.onerror = function () { 1048 | events.trigger('load-error'); 1049 | }; 1050 | events.trigger('load-start'); 1051 | newImage.src = imageSource; 1052 | } 1053 | }; 1054 | 1055 | this.setMaxDimensions = function (width, height) { 1056 | maxCanvasDims = [width, height]; 1057 | 1058 | if (image !== null) { 1059 | var curWidth = ctx.canvas.width, 1060 | curHeight = ctx.canvas.height; 1061 | 1062 | var imageDims = [image.width, image.height], 1063 | imageRatio = image.width / image.height, 1064 | canvasDims = imageDims; 1065 | 1066 | if (canvasDims[0] > maxCanvasDims[0]) { 1067 | canvasDims[0] = maxCanvasDims[0]; 1068 | canvasDims[1] = canvasDims[0] / imageRatio; 1069 | } else if (canvasDims[0] < minCanvasDims[0]) { 1070 | canvasDims[0] = minCanvasDims[0]; 1071 | canvasDims[1] = canvasDims[0] / imageRatio; 1072 | } 1073 | if (canvasDims[1] > maxCanvasDims[1]) { 1074 | canvasDims[1] = maxCanvasDims[1]; 1075 | canvasDims[0] = canvasDims[1] * imageRatio; 1076 | } else if (canvasDims[1] < minCanvasDims[1]) { 1077 | canvasDims[1] = minCanvasDims[1]; 1078 | canvasDims[0] = canvasDims[1] * imageRatio; 1079 | } 1080 | elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]).css({ 1081 | 'margin-left': -canvasDims[0] / 2 + 'px', 1082 | 'margin-top': -canvasDims[1] / 2 + 'px' 1083 | }); 1084 | 1085 | var ratioNewCurWidth = ctx.canvas.width / curWidth, 1086 | ratioNewCurHeight = ctx.canvas.height / curHeight, 1087 | ratioMin = Math.min(ratioNewCurWidth, ratioNewCurHeight); 1088 | 1089 | //TODO: use top left corner point 1090 | theArea.setSize({ 1091 | w: theArea.getSize().w * ratioMin, 1092 | h: theArea.getSize().h * ratioMin 1093 | }); 1094 | var center = theArea.getCenterPoint(); 1095 | theArea.setCenterPoint({ 1096 | x: center.x * ratioNewCurWidth, 1097 | y: center.y * ratioNewCurHeight 1098 | }); 1099 | 1100 | } else { 1101 | elCanvas.prop('width', 0).prop('height', 0).css({'margin-top': 0}); 1102 | } 1103 | 1104 | drawScene(); 1105 | 1106 | }; 1107 | 1108 | this.setAreaMinSize = function (size) { 1109 | if (angular.isUndefined(size)) { 1110 | return; 1111 | } 1112 | size = { 1113 | w: parseInt(size.w, 10), 1114 | h: parseInt(size.h, 10) 1115 | }; 1116 | if (!isNaN(size.w) && !isNaN(size.h)) { 1117 | theArea.setMinSize(size); 1118 | drawScene(); 1119 | } 1120 | }; 1121 | 1122 | this.getResultImageSize = function () { 1123 | if (resImgSize == "selection") { 1124 | return theArea.getSize(); 1125 | } 1126 | 1127 | return resImgSize; 1128 | }; 1129 | this.setResultImageSize = function (size) { 1130 | if (angular.isUndefined(size)) { 1131 | return; 1132 | } 1133 | 1134 | //allow setting of size to "selection" for mirroring selection's dimensions 1135 | if (angular.isString(size)) { 1136 | resImgSize = size; 1137 | return; 1138 | } 1139 | 1140 | //allow scalar values for square-like selection shapes 1141 | if (angular.isNumber(size)) { 1142 | size = parseInt(size, 10); 1143 | size = { 1144 | w: size, 1145 | h: size 1146 | }; 1147 | } 1148 | 1149 | size = { 1150 | w: parseInt(size.w, 10), 1151 | h: parseInt(size.h, 10) 1152 | }; 1153 | if (!isNaN(size.w) && !isNaN(size.h)) { 1154 | resImgSize = size; 1155 | drawScene(); 1156 | } 1157 | }; 1158 | 1159 | // returns a string of the selection area's type 1160 | this.getAreaType = function () { 1161 | return theArea.getType(); 1162 | } 1163 | 1164 | this.setAreaType = function (type) { 1165 | var center = theArea.getCenterPoint(); 1166 | var curSize = theArea.getSize(), 1167 | curMinSize = theArea.getMinSize(), 1168 | curX = center.x, 1169 | curY = center.y; 1170 | 1171 | var AreaClass = CropAreaCircle; 1172 | if (type === 'square') { 1173 | AreaClass = CropAreaSquare; 1174 | } else if (type === 'rectangle') { 1175 | AreaClass = CropAreaRectangle; 1176 | } 1177 | theArea = new AreaClass(ctx, events); 1178 | theArea.setMinSize(curMinSize); 1179 | theArea.setSize(curSize); 1180 | 1181 | //TODO: use top left point 1182 | theArea.setCenterPoint({x: curX, y: curY}); 1183 | 1184 | // resetCropHost(); 1185 | if (image !== null) { 1186 | theArea.setImage(image); 1187 | } 1188 | 1189 | drawScene(); 1190 | }; 1191 | 1192 | /* Life Cycle begins */ 1193 | 1194 | // Init Context var 1195 | ctx = elCanvas[0].getContext('2d'); 1196 | 1197 | // Init CropArea 1198 | theArea = new CropAreaCircle(ctx, events); 1199 | 1200 | // Init Mouse Event Listeners 1201 | $document.on('mousemove', onMouseMove); 1202 | elCanvas.on('mousedown', onMouseDown); 1203 | $document.on('mouseup', onMouseUp); 1204 | 1205 | // Init Touch Event Listeners 1206 | $document.on('touchmove', onMouseMove); 1207 | elCanvas.on('touchstart', onMouseDown); 1208 | $document.on('touchend', onMouseUp); 1209 | 1210 | // CropHost Destructor 1211 | this.destroy = function () { 1212 | $document.off('mousemove', onMouseMove); 1213 | elCanvas.off('mousedown', onMouseDown); 1214 | $document.off('mouseup', onMouseMove); 1215 | 1216 | $document.off('touchmove', onMouseMove); 1217 | elCanvas.off('touchstart', onMouseDown); 1218 | $document.off('touchend', onMouseMove); 1219 | 1220 | elCanvas.remove(); 1221 | }; 1222 | }; 1223 | 1224 | }]); 1225 | 1226 | 1227 | crop.factory('cropPubSub', [function () { 1228 | return function () { 1229 | var events = {}; 1230 | // Subscribe 1231 | this.on = function (names, handler) { 1232 | names.split(' ').forEach(function (name) { 1233 | if (!events[name]) { 1234 | events[name] = []; 1235 | } 1236 | events[name].push(handler); 1237 | }); 1238 | return this; 1239 | }; 1240 | // Publish 1241 | this.trigger = function (name, args) { 1242 | angular.forEach(events[name], function (handler) { 1243 | handler.call(null, args); 1244 | }); 1245 | return this; 1246 | }; 1247 | }; 1248 | }]); 1249 | 1250 | crop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function ($timeout, CropHost, CropPubSub) { 1251 | return { 1252 | restrict: 'E', 1253 | scope: { 1254 | image: '=', 1255 | resultImage: '=', 1256 | resultImageData: '=', 1257 | 1258 | changeOnFly: '=', 1259 | areaCoords: '=', 1260 | areaType: '@', 1261 | areaMinSize: '=', 1262 | resultImageSize: '=', 1263 | 1264 | onChange: '&', 1265 | onLoadBegin: '&', 1266 | onLoadDone: '&', 1267 | onLoadError: '&' 1268 | }, 1269 | template: '', 1270 | controller: function ($scope/*, $attrs, $element*/) { 1271 | $scope.events = new CropPubSub(); 1272 | }, 1273 | link: function (scope, element/*, attrs*/) { 1274 | // Init Events Manager 1275 | var events = scope.events; 1276 | 1277 | // Init Crop Host 1278 | var cropHost = new CropHost(element.find('canvas'), {}, events); 1279 | 1280 | // Store Result Image to check if it's changed 1281 | var storedResultImage; 1282 | 1283 | var updateResultImage = function (scope) { 1284 | var resultImageObj = cropHost.getResultImage(); 1285 | if (resultImageObj) { 1286 | var resultImage = resultImageObj.dataURI; 1287 | if (storedResultImage !== resultImage) { 1288 | storedResultImage = resultImage; 1289 | if (angular.isDefined(scope.resultImage)) { 1290 | scope.resultImage = resultImage; 1291 | } 1292 | if (angular.isDefined(scope.resultImageData)) { 1293 | scope.resultImageData = resultImageObj.imageData; 1294 | } 1295 | 1296 | updateAreaCoords(scope); 1297 | scope.onChange({ 1298 | $dataURI: scope.resultImage, 1299 | $imageData: scope.resultImageData 1300 | }); 1301 | 1302 | 1303 | } 1304 | } 1305 | }; 1306 | 1307 | var updateAreaCoords = function (scope) { 1308 | var areaCoords = cropHost.getAreaCoords(); 1309 | scope.areaCoords = areaCoords; 1310 | }; 1311 | 1312 | // Wrapper to safely exec functions within $apply on a running $digest cycle 1313 | var fnSafeApply = function (fn) { 1314 | return function () { 1315 | $timeout(function () { 1316 | scope.$apply(function (scope) { 1317 | fn(scope); 1318 | }); 1319 | }); 1320 | }; 1321 | }; 1322 | 1323 | // Setup CropHost Event Handlers 1324 | events 1325 | .on('load-start', fnSafeApply(function (scope) { 1326 | scope.onLoadBegin({}); 1327 | })) 1328 | .on('load-done', fnSafeApply(function (scope) { 1329 | scope.onLoadDone({}); 1330 | })) 1331 | .on('load-error', fnSafeApply(function (scope) { 1332 | scope.onLoadError({}); 1333 | })) 1334 | .on('area-move area-resize', fnSafeApply(function (scope) { 1335 | if (!!scope.changeOnFly) { 1336 | updateResultImage(scope); 1337 | } 1338 | })) 1339 | .on('area-move-end area-resize-end image-updated', fnSafeApply(function (scope) { 1340 | updateResultImage(scope); 1341 | })); 1342 | 1343 | // Sync CropHost with Directive's options 1344 | scope.$watch('image', function () { 1345 | cropHost.setNewImageSource(scope.image); 1346 | }); 1347 | scope.$watch('areaType', function () { 1348 | cropHost.setAreaType(scope.areaType); 1349 | updateResultImage(scope); 1350 | }); 1351 | scope.$watch('areaMinSize', function () { 1352 | cropHost.setAreaMinSize(scope.areaMinSize); 1353 | updateResultImage(scope); 1354 | }); 1355 | scope.$watch('resultImageSize', function () { 1356 | cropHost.setResultImageSize(scope.resultImageSize); 1357 | updateResultImage(scope); 1358 | }); 1359 | 1360 | // Update CropHost dimensions when the directive element is resized 1361 | scope.$watch( 1362 | function () { 1363 | return [element[0].clientWidth, element[0].clientHeight]; 1364 | }, 1365 | function (value) { 1366 | cropHost.setMaxDimensions(value[0], value[1]); 1367 | updateResultImage(scope); 1368 | }, 1369 | true 1370 | ); 1371 | 1372 | // Destroy CropHost Instance when the directive is destroying 1373 | scope.$on('$destroy', function () { 1374 | cropHost.destroy(); 1375 | }); 1376 | } 1377 | }; 1378 | }]); 1379 | 1380 | }()); 1381 | --------------------------------------------------------------------------------