1 |
|
2 | import { addEvent, removeEvent } from './utils/DOM';
|
3 | import { BunnyImage } from './file/image';
|
4 |
|
5 | export 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 |
|
16 | export 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 |
|
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 |
|
127 | export 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 |
|
231 |
|
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 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
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 | };
|