├── 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 |
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 | 
36 |
37 | 
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 |
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 |
--------------------------------------------------------------------------------