UNPKG

10.3 kBJavaScriptView Raw
1
2import { addEvent, removeEvent } from './utils/DOM';
3import { BunnyImage } from './file/image';
4
5export const ImageProcessorConfig = {
6 tagName: 'imageprocessor',
7 tagNameCursor: 'imagecursor',
8 selectorBtnSave: '[pid="save"]',
9 selectorBtnRotate: '[pid="rotate"]',
10 attrQuality: 'quality',
11 defaultQuality: 0.7,
12 attrOutputSize: 'outputsize',
13 defaultOutputSize: '300',
14};
15
16export const ImageProcessorUI = {
17
18 Config: ImageProcessorConfig,
19
20 getAll(container = document) {
21 return container.getElementsByTagName(this.Config.tagName);
22 },
23
24 getImage(processor) {
25 if (processor.__img === undefined) {
26 processor.__img = processor.getElementsByTagName('img')[0] || false;
27 }
28 return processor.__img;
29 },
30
31 getCursor(processor) {
32 if (processor.__cursor === undefined) {
33 processor.__cursor = processor.getElementsByTagName(this.Config.tagNameCursor)[0] || false;
34 }
35 return processor.__cursor;
36 },
37
38 getSaveBtn(processor) {
39 return processor.querySelector(this.Config.selectorBtnSave) || false;
40 },
41
42 getRotateBtn(processor) {
43 return processor.querySelector(this.Config.selectorBtnSave) || false;
44 },
45
46 checkCursorWillBeInsideX(processor, newX) {
47 const cursor = this.getCursor(processor);
48 const img = this.getImage(processor);
49 return newX >= 0 && cursor.getBoundingClientRect().width + newX <= img.getBoundingClientRect().width;
50 },
51
52 checkCursorWillBeInsideY(processor, newY) {
53 const cursor = this.getCursor(processor);
54 const img = this.getImage(processor);
55 return newY >= 0 && cursor.getBoundingClientRect().height + newY <= img.getBoundingClientRect().height;
56 },
57
58 /**
59 * Automatically center a cursor taking full images's smallest side length
60 */
61 centerCursor(processor) {
62 const img = this.getImage(processor);
63 const cursor = this.getCursor(processor);
64 let size = 0;
65 const imgrect = img.getBoundingClientRect();
66 if (imgrect.width > imgrect.height) {
67 size = imgrect.height;
68 cursor.style.left = (imgrect.width - size) / 2 + 'px';
69 cursor.style.top = 0 + 'px';
70 } else {
71 size = imgrect.width;
72 cursor.style.left = 0 + 'px';
73 cursor.style.top = (imgrect.height - size) / 2 + 'px';
74 }
75 cursor.style.width = size + 'px';
76 cursor.style.height = size + 'px';
77 },
78
79 moveCursor(processor, deltaX, deltaY) {
80 const img = this.getImage(processor);
81 const cursor = this.getCursor(processor);
82 const imgrect = img.getBoundingClientRect();
83 const cursorrect = cursor.getBoundingClientRect();
84
85 if (this.checkCursorWillBeInsideX(processor, cursor.offsetLeft + deltaX)) {
86 cursor.style.left = cursor.offsetLeft + deltaX + 'px';
87 } else if (cursor.offsetLeft + deltaX < 0) {
88 cursor.style.left = '0px';
89 } else {
90 cursor.style.left = imgrect.width - cursorrect.width;
91 }
92
93 if (this.checkCursorWillBeInsideY(processor, cursor.offsetTop + deltaY)) {
94 cursor.style.top = cursor.offsetTop + deltaY + 'px';
95 } else if (cursor.offsetTop + deltaY < 0) {
96 cursor.style.top = '0px';
97 } else {
98 cursor.style.top = imgrect.height - cursorrect.height;
99 }
100 },
101
102 resizeCursor(processor, deltaX, deltaY = null) {
103 const img = this.getImage(processor);
104 const cursor = this.getCursor(processor);
105 const imgrect = img.getBoundingClientRect();
106 const cursorrect = cursor.getBoundingClientRect();
107
108 let width = cursorrect.width + deltaX;
109 let height = 0;
110 if (deltaY === null) {
111 height = cursorrect.height + deltaX;
112 } else {
113 height = cursorrect.height + deltaY;
114 }
115
116 if (cursor.offsetTop + height <= imgrect.height &&
117 cursor.offsetLeft + width <= imgrect.width) {
118 cursor.style.width = width + 'px';
119 cursor.style.height = height + 'px';
120 return true;
121 }
122 return false;
123 },
124
125};
126
127export const ImageProcessor = {
128
129 Config: ImageProcessorConfig,
130 UI: ImageProcessorUI,
131
132 init(processor, src = null, onSave = null) {
133 if (src !== null) {
134 this.setImage(processor, src);
135 }
136
137 this.addEvents(processor);
138
139 if (onSave !== null) {
140 this.onSave(processor, onSave);
141 }
142 },
143
144 deinit(processor) {
145 this.removeEvents(processor);
146 delete processor.__on_save;
147 },
148
149 initAll(container = document) {
150 [].forEach.call(this.UI.getAll(container), processor => {
151 this.init(processor);
152 });
153 },
154
155 deinitAll(container = document) {
156 [].forEach.call(this.UI.getAll(container), processor => {
157 this.deinit(processor);
158 });
159 },
160
161 addEvents(processor) {
162 const cursor = this.UI.getCursor(processor);
163 const img = this.UI.getImage(processor);
164 const saveBtn = this.UI.getSaveBtn(processor);
165 processor.__move_start = addEvent(cursor, 'mousedown', this.handleBeginMove.bind(this, processor));
166 processor.__move_end = addEvent(window, 'mouseup', this.handleEndMove.bind(this, processor));
167 processor.__zoom = addEvent(processor, 'wheel', this.handleZoom.bind(this, processor));
168 processor.__save = addEvent(saveBtn, 'click', this.handleSave.bind(this, processor));
169 processor.__img_load = addEvent(img, 'load', this.handleImgLoad.bind(this, processor));
170 processor.__keypress = addEvent(cursor, 'keypress', this.handleKeyPress.bind(this, processor));
171 },
172
173 removeEvents(processor) {
174 const cursor = this.UI.getCursor(processor);
175 const img = this.UI.getImage(processor);
176 const saveBtn = this.UI.getSaveBtn(processor);
177 delete removeEvent(cursor, 'mousedown', processor.__move_start);
178 delete removeEvent(window, 'mouseup', processor.__move_end);
179 delete removeEvent(processor, 'wheel', processor.__zoom);
180 delete removeEvent(saveBtn, 'click', processor.__save);
181 delete removeEvent(img, 'load', processor.__img_load);
182 delete removeEvent(cursor, 'keypress', processor.__keypress);
183 },
184
185 handleKeyPress(processor, e) {
186 e.preventDefault();
187 if (processor.__key_speed === undefined) {
188 processor.__key_speed = 0;
189 }
190 processor.__key_speed++;
191 if (processor.__key_timeout !== undefined) {
192 clearTimeout(processor.__key_timeout);
193 }
194 processor.__key_timeout = setTimeout(() => {
195 delete processor.__key_speed;
196 delete processor.__key_timeout;
197 }, 100);
198
199 if (e.keyCode === KEY_ARROW_RIGHT) {
200 if (e.shiftKey) {
201 this.UI.resizeCursor(processor, processor.__key_speed);
202 } else {
203 this.UI.moveCursor(processor, processor.__key_speed, 0);
204 }
205 } else if (e.keyCode === KEY_ARROW_DOWN) {
206 if (e.shiftKey) {
207 this.UI.resizeCursor(processor, -processor.__key_speed);
208 } else {
209 this.UI.moveCursor(processor, 0, processor.__key_speed);
210 }
211 } else if (e.keyCode === KEY_ARROW_LEFT) {
212 if (e.shiftKey) {
213 this.UI.resizeCursor(processor, -processor.__key_speed);
214 } else {
215 this.UI.moveCursor(processor, -processor.__key_speed, 0);
216 }
217 } else if (e.keyCode === KEY_ARROW_UP) {
218 if (e.shiftKey) {
219 this.UI.resizeCursor(processor, processor.__key_speed);
220 } else {
221 this.UI.moveCursor(processor, 0, -processor.__key_speed);
222 }
223 } else if (e.keyCode === KEY_ENTER) {
224 this.handleSave(processor);
225 }
226 },
227
228 /**
229 *
230 * @param processor
231 * @param {String|File|Blob} src
232 */
233 setImage(processor, src) {
234 if (typeof src !== 'string') {
235 src = URL.createObjectURL(src);
236 }
237 this.UI.getImage(processor).src = src;
238 },
239
240 setQuality(processor, quality) {
241 processor.setAttribute(this.Config.attrQuality, quality);
242 },
243
244 getQuality(processor) {
245 let q = processor.getAttribute(this.Config.attrQuality);
246 if (q) {
247 q = parseFloat(q);
248 } else {
249 q = this.Config.defaultQuality;
250 }
251 return q;
252 },
253
254 setOutputSize(processor, size) {
255 processor.setAttribute(this.Config.attrOutputSize, size);
256 },
257
258 getOutputSize(processor) {
259 let s = processor.getAttribute(this.Config.attrOutputSize);
260 if (s) {
261 s = parseFloat(s);
262 } else {
263 s = this.Config.defaultOutputSize;
264 }
265 return s;
266 },
267
268 handleZoom(processor, e) {
269 e.preventDefault();
270 const deltaX = -e.deltaY * 2;
271 this.UI.resizeCursor(processor, deltaX);
272 },
273
274 handleImgLoad(processor) {
275 this.UI.centerCursor(processor);
276 this.UI.getCursor(processor).focus();
277 },
278
279 setMoveStartPos(processor, e) {
280 processor.__startX = e.layerX;
281 processor.__startY = e.layerY;
282 },
283
284 getMoveStartPos(processor) {
285 return [
286 processor.__startX,
287 processor.__startY
288 ];
289 },
290
291 handleMove(processor, e) {
292 const startPos = this.getMoveStartPos(processor);
293 const endX = e.layerX;
294 const endY = e.layerY;
295 const deltaX = endX - startPos[0];
296 const deltaY = endY - startPos[1];
297 this.UI.moveCursor(processor, deltaX, deltaY);
298 },
299
300 handleBeginMove(processor, e) {
301 const cursor = this.UI.getCursor(processor);
302 cursor.focus();
303 cursor.classList.add('moving');
304 this.setMoveStartPos(processor, e);
305 processor.__moving = addEvent(cursor, 'mousemove', this.handleMove.bind(this, processor));
306 },
307
308 handleEndMove(processor, e) {
309 const cursor = this.UI.getCursor(processor);
310 cursor.classList.remove('moving');
311 delete removeEvent(cursor, 'mousemove', processor.__moving);
312 },
313
314
315 /**
316 * Process an image - crops by selected region, resize to $size x $size
317 * makes an image/jpeg with $quality
318 * and returns a base64 string can be used in src attribute or for further custom file processing
319 *
320 * @param processor
321 * @param size
322 * @param quality
323 * @returns {string}
324 */
325 processImage(processor, size, quality) {
326 let canv = BunnyImage.cropByCursor(this.UI.getImage(processor), this.UI.getCursor(processor));
327 canv = BunnyImage.resizeCanvas(canv, size);
328 const base64 = canv.toDataURL('image/jpeg', quality);
329 return base64;
330 },
331
332 handleSave(processor) {
333 const size = this.getOutputSize(processor);
334 const quality = this.getQuality(processor);
335 const src = this.processImage(processor, size, quality);
336 if (processor.__on_save !== undefined) {
337 processor.__on_save.forEach(cb => {
338 cb(src);
339 });
340 }
341 },
342
343 onSave(processor, callback) {
344 if (processor.__on_save === undefined) processor.__on_save = [];
345 processor.__on_save.push(callback);
346 },
347
348};