├── .gitignore
├── LICENSE
├── README.md
├── angular-cropper.js
├── bower.json
├── package.json
└── test.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 |
27 | bower_components
28 |
29 | *.swp
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Tim Whitbeck
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-cropper
2 | ===============
3 |
4 | > Touch-enabled image cropper for AngularJS
5 |
6 | [Demo](http://plnkr.co/edit/s96ZCYmP5ELtqdIMieKC?p=preview)
7 |
8 | ### Get it
9 | ```sh
10 | $ bower install angular-cropper
11 | ```
12 |
13 | or
14 |
15 | ```sh
16 | $ npm install angular-cropper
17 | ```
18 |
19 | ### Use it
20 | * Include files in your html
21 | * `bower_components/angular-file-reader/angular-file-reader.js`
22 | * `bower_components/angular-cropper/angular-cropper.js`
23 | * Include `tw.directives.cropper` in your module dependencies
24 | * `angular.module('myApp', ['tw.directives.cropper']);`
25 | * Place the `tw-cropper` attribute on a canvas
26 | * ``
27 | * Give your cropper a `source`
28 | * ``
29 | * `source` must be a File
30 | * See the demo for one way to get a File to your scope
31 | * Or use [twhitbeck/angular-file-input](https://github.com/twhitbeck/angular-file-input)
32 | * Get the dataURL of your crop with controller `toDataURL` method
33 | * ``
34 | * That's it!
35 |
--------------------------------------------------------------------------------
/angular-cropper.js:
--------------------------------------------------------------------------------
1 | angular.module('tw.directives.cropper', ['tw.services.fileReader']);
2 |
3 | angular.module('tw.directives.cropper').directive('twCropper', ['$parse', '$window', '$document', 'twFileReader', function($parse, $window, $document, twFileReader) {
4 | var document = $document[0],
5 | Math = $window.Math;
6 |
7 | return {
8 | restrict: 'A',
9 | controller: ['$scope', '$attrs', '$element', function($scope, $attrs, $element) {
10 | var canvas = $element[0];
11 |
12 | // If twCropper attribute is provided
13 | if ($attrs.twCropper) {
14 | // Publish this controller to the scope via the expression
15 | $parse($attrs.twCropper).assign($scope, this);
16 | }
17 |
18 | this.toDataURL = function toDataURL() {
19 | return canvas.toDataURL();
20 | };
21 | }],
22 | link: function(scope, el, attrs) {
23 | if (angular.lowercase(el[0].nodeName) !== 'canvas') {
24 | return;
25 | }
26 |
27 | var canvas = el[0];
28 | var ctx = canvas.getContext('2d');
29 | var img = new Image();
30 | var x, y, scale, maxScale, minScale;
31 |
32 | var draw = function draw() {
33 | var sx = x,
34 | sy = y,
35 | sWidth = canvas.width * scale,
36 | sHeight = canvas.height * scale,
37 | dx = 0,
38 | dy = 0,
39 | dWidth = canvas.width,
40 | dHeight = canvas.height;
41 |
42 | ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
43 | };
44 |
45 | var zoom = function zoom(dScale) {
46 | var s = scale;
47 |
48 | scale += dScale;
49 |
50 | if (scale < minScale) {
51 | scale = minScale;
52 | } else if (scale > maxScale) {
53 | scale = maxScale;
54 | }
55 |
56 | var newWidth = scale * canvas.width;
57 | var newHeight = scale * canvas.height;
58 | var oldWidth = s * canvas.width;
59 | var oldHeight = s * canvas.height;
60 |
61 | var dWidth = newWidth - oldWidth;
62 | var dHeight = newHeight - oldHeight;
63 |
64 | x -= dWidth / 2;
65 | y -= dHeight / 2;
66 |
67 | if (x < 0) {
68 | x = 0;
69 | } else {
70 | if (x + newWidth > img.width) {
71 | x = img.width - newWidth;
72 | }
73 | }
74 |
75 | if (y < 0) {
76 | y = 0;
77 | } else {
78 | if (y + newHeight > img.height) {
79 | y = img.height - newHeight;
80 | }
81 | }
82 | };
83 |
84 | scope.$watch(attrs.source, function(newVal) {
85 | if (!newVal) {
86 | ctx.clearRect(0, 0, canvas.width, canvas.height);
87 |
88 | return;
89 | }
90 |
91 | twFileReader.readAsDataURL(newVal).then(function(dataURL) {
92 | img.onload = function() {
93 | x = 0;
94 | y = 0;
95 | scale = minScale = 1;
96 |
97 | if (img.width > img.height) {
98 | maxScale = img.height / canvas.height;
99 | } else {
100 | maxScale = img.width / canvas.width;
101 |
102 | }
103 |
104 | if (img.height < canvas.height) {
105 | scale = minScale = maxScale;
106 | }
107 |
108 | if (img.width < canvas.width) {
109 | scale = minScale = maxScale;
110 | }
111 |
112 | draw();
113 | };
114 |
115 | img.src = dataURL;
116 | });
117 | });
118 |
119 | var sx, sy;
120 | var move = function move(newX, newY) {
121 | x += (sx - newX) * scale;
122 | y += (sy - newY) * scale;
123 |
124 | if (x < 0) {
125 | x = 0;
126 | } else {
127 | var scaledWidth = canvas.width * scale;
128 |
129 | if (x + scaledWidth > img.width) {
130 | x = img.width - scaledWidth;
131 | }
132 | }
133 |
134 | if (y < 0) {
135 | y = 0;
136 | } else {
137 | var scaledHeight = canvas.height * scale;
138 |
139 | if (y + scaledHeight > img.height) {
140 | y = img.height - scaledHeight;
141 | }
142 | }
143 |
144 | draw();
145 |
146 | sx = newX;
147 | sy = newY;
148 | };
149 |
150 | var mousemove = function mousemove(e) {
151 | move(e.clientX, e.clientY);
152 | };
153 |
154 | var d = null;
155 | var pinch = function pinch(touch1, touch2) {
156 | var x1 = touch1.clientX;
157 | var y1 = touch1.clientY;
158 | var x2 = touch2.clientX;
159 | var y2 = touch2.clientY;
160 |
161 | var newD = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
162 |
163 | if (d !== null) {
164 | var dx = newD - d;
165 |
166 | zoom(dx * -.015);
167 |
168 | draw();
169 | }
170 |
171 | d = newD;
172 | };
173 |
174 | var touchmove = function touchmove(e) {
175 | // jQuery doesn't copy touches over to its event, so we need to go through originalEvent in that case
176 | e = e.originalEvent || e;
177 |
178 | if (e.touches.length === 1) {
179 | move(e.touches[0].clientX, e.touches[0].clientY);
180 | } else if (e.touches.length === 2) {
181 | pinch(e.touches[0], e.touches[1]);
182 | }
183 | };
184 |
185 | var start = function(x, y) {
186 | if (!img.src) {
187 | return;
188 | }
189 |
190 | sx = x;
191 | sy = y;
192 |
193 | $document.on('mousemove', mousemove);
194 | $document.on('touchmove', touchmove);
195 | };
196 |
197 | var mousedown = function mousedown(e) {
198 | start(e.clientX, e.clientY);
199 | };
200 |
201 | var touchstart = function touchstart(e) {
202 | // jQuery doesn't copy touches over to its event, so we need to go through originalEvent in that case
203 | e = e.originalEvent || e;
204 |
205 | e.preventDefault();
206 |
207 | if (e.touches.length === 1) {
208 | start(e.touches[0].clientX, e.touches[0].clientY);
209 | }
210 | };
211 |
212 | el.on('mousedown', mousedown);
213 | el.on('touchstart', touchstart);
214 |
215 | var end = function end() {
216 | $document.off('mousemove', mousemove);
217 | $document.off('touchmove', touchmove);
218 |
219 | d = null;
220 | };
221 |
222 | $document.on('mouseup touchend', end);
223 |
224 | el.on('wheel', function(e) {
225 | e.preventDefault();
226 |
227 | e = e.originalEvent || e;
228 |
229 | if (!img.src) {
230 | return;
231 | }
232 |
233 | if (e.deltaY < 0) {
234 | zoom(-.15);
235 | } else {
236 | zoom(.15);
237 | }
238 |
239 | draw();
240 | });
241 | }
242 | };
243 | }]);
244 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-cropper",
3 | "main": "angular-cropper.js",
4 | "version": "0.8.4",
5 | "homepage": "https://github.com/twhitbeck/angular-cropper",
6 | "authors": [
7 | "Tim Whitbeck "
8 | ],
9 | "description": "Touch-enabled, client side image cropping with AngularJS",
10 | "keywords": [
11 | "angular",
12 | "angularjs",
13 | "image",
14 | "crop",
15 | "cropping",
16 | "cropper",
17 | "directive"
18 | ],
19 | "license": "MIT",
20 | "ignore": [
21 | "**/.*",
22 | "node_modules",
23 | "bower_components",
24 | "test",
25 | "tests"
26 | ],
27 | "dependencies": {
28 | "angular": "^1.3",
29 | "angular-file-reader": "~0.0.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-cropper",
3 | "version": "0.9.0",
4 | "description": "A simple image cropper angular.js (1.x) directive",
5 | "main": "angular-cropper.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/twhitbeck/angular-cropper.git"
9 | },
10 | "keywords": [
11 | "angular",
12 | "angular.js",
13 | "angularjs",
14 | "crop",
15 | "image",
16 | "mobile",
17 | "cropper"
18 | ],
19 | "author": "twhitbeck",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/twhitbeck/angular-cropper/issues"
23 | },
24 | "homepage": "https://github.com/twhitbeck/angular-cropper#readme",
25 | "dependencies": {
26 | "angular-file-reader": "^1.0.0"
27 | },
28 | "peerDependencies": {
29 | "angular": "^1.4.8"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
46 |
47 |
48 |
--------------------------------------------------------------------------------