scratch – Blame information for rev 84
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
84 | office | 1 | window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {}; |
2 | |||
3 | /** |
||
4 | * pass the id of the html element to put the drawing board into |
||
5 | * and some options : { |
||
6 | * controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default |
||
7 | * instead of simple strings, you can pass an object to define a control opts |
||
8 | * ie ['Color', { Navigation: { reset: false }}] |
||
9 | * controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center" |
||
10 | * background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default |
||
11 | * color: pencil color ("#000000" by default) |
||
12 | * size: pencil size (3 by default) |
||
13 | * webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back |
||
14 | * droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it, |
||
15 | * errorMessage: html string to put in the board's element on browsers that don't support canvas. |
||
16 | * stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default |
||
17 | * } |
||
18 | */ |
||
19 | DrawingBoard.Board = function(id, opts) { |
||
20 | this.opts = this.mergeOptions(opts); |
||
21 | |||
22 | this.ev = new DrawingBoard.Utils.MicroEvent(); |
||
23 | |||
24 | this.id = id; |
||
25 | this.$el = $(document.getElementById(id)); |
||
26 | if (!this.$el.length) |
||
27 | return false; |
||
28 | |||
29 | var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>'; |
||
30 | if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>'; |
||
31 | else tpl = '<div class="drawing-board-controls"></div>' + tpl; |
||
32 | |||
33 | this.$el.addClass('drawing-board').append(tpl); |
||
34 | this.dom = { |
||
35 | $canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'), |
||
36 | $canvas: this.$el.find('.drawing-board-canvas'), |
||
37 | $cursor: this.$el.find('.drawing-board-cursor'), |
||
38 | $controls: this.$el.find('.drawing-board-controls') |
||
39 | }; |
||
40 | |||
41 | $.each(['left', 'right', 'center'], $.proxy(function(n, val) { |
||
42 | if (this.opts.controlsPosition.indexOf(val) > -1) { |
||
43 | this.dom.$controls.attr('data-align', val); |
||
44 | return false; |
||
45 | } |
||
46 | }, this)); |
||
47 | |||
48 | this.canvas = this.dom.$canvas.get(0); |
||
49 | this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null; |
||
50 | this.color = this.opts.color; |
||
51 | |||
52 | if (!this.ctx) { |
||
53 | if (this.opts.errorMessage) |
||
54 | this.$el.html(this.opts.errorMessage); |
||
55 | return false; |
||
56 | } |
||
57 | |||
58 | this.storage = this._getStorage(); |
||
59 | |||
60 | this.initHistory(); |
||
61 | //init default board values before controls are added (mostly pencil color and size) |
||
62 | this.reset({ webStorage: false, history: false, background: false }); |
||
63 | //init controls (they will need the default board values to work like pencil color and size) |
||
64 | this.initControls(); |
||
65 | //set board's size after the controls div is added |
||
66 | this.resize(); |
||
67 | //reset the board to take all resized space |
||
68 | this.reset({ webStorage: false, history: false, background: true }); |
||
69 | this.restoreWebStorage(); |
||
70 | this.initDropEvents(); |
||
71 | this.initDrawEvents(); |
||
72 | }; |
||
73 | |||
74 | |||
75 | |||
76 | DrawingBoard.Board.defaultOpts = { |
||
77 | controls: ['Color', 'DrawingMode', 'Size', 'Navigation'], |
||
78 | controlsPosition: "top left", |
||
79 | color: "#000000", |
||
80 | size: 1, |
||
81 | background: "#fff", |
||
82 | eraserColor: "background", |
||
83 | fillTolerance: 100, |
||
84 | fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default |
||
85 | webStorage: 'session', |
||
86 | droppable: false, |
||
87 | enlargeYourContainer: false, |
||
88 | errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>", |
||
89 | stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true |
||
90 | }; |
||
91 | |||
92 | |||
93 | |||
94 | DrawingBoard.Board.prototype = { |
||
95 | |||
96 | mergeOptions: function(opts) { |
||
97 | opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts); |
||
98 | if (!opts.background && opts.eraserColor === "background") |
||
99 | opts.eraserColor = "transparent"; |
||
100 | return opts; |
||
101 | }, |
||
102 | |||
103 | /** |
||
104 | * Canvas reset/resize methods: put back the canvas to its default values |
||
105 | * |
||
106 | * depending on options, can set color, size, background back to default values |
||
107 | * and store the reseted canvas in webstorage and history queue |
||
108 | * |
||
109 | * resize values depend on the `enlargeYourContainer` option |
||
110 | */ |
||
111 | |||
112 | reset: function(opts) { |
||
113 | opts = $.extend({ |
||
114 | color: this.opts.color, |
||
115 | size: this.opts.size, |
||
116 | webStorage: true, |
||
117 | history: true, |
||
118 | background: false |
||
119 | }, opts); |
||
120 | |||
121 | this.setMode('pencil'); |
||
122 | |||
123 | if (opts.background) { |
||
124 | this.resetBackground(this.opts.background, $.proxy(function() { |
||
125 | if (opts.history) this.saveHistory(); |
||
126 | }, this)); |
||
127 | } |
||
128 | |||
129 | if (opts.color) this.setColor(opts.color); |
||
130 | if (opts.size) this.ctx.lineWidth = opts.size; |
||
131 | |||
132 | this.ctx.lineCap = "round"; |
||
133 | this.ctx.lineJoin = "round"; |
||
134 | // this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width); |
||
135 | |||
136 | if (opts.webStorage) this.saveWebStorage(); |
||
137 | |||
138 | // if opts.background we already dealt with the history |
||
139 | if (opts.history && !opts.background) this.saveHistory(); |
||
140 | |||
141 | this.blankCanvas = this.getImg(); |
||
142 | |||
143 | this.ev.trigger('board:reset', opts); |
||
144 | }, |
||
145 | |||
146 | resetBackground: function(background, callback) { |
||
147 | background = background || this.opts.background; |
||
148 | |||
149 | var bgIsColor = DrawingBoard.Utils.isColor(background); |
||
150 | var prevMode = this.getMode(); |
||
151 | this.setMode('pencil'); |
||
152 | this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); |
||
153 | if (bgIsColor) { |
||
154 | this.ctx.fillStyle = background; |
||
155 | this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); |
||
156 | this.history.initialize(this.getImg()); |
||
157 | if (callback) callback(); |
||
158 | } else if (background) |
||
159 | this.setImg(background, { |
||
160 | callback: $.proxy(function() { |
||
161 | this.history.initialize(this.getImg()); |
||
162 | if (callback) callback(); |
||
163 | }, this) |
||
164 | }); |
||
165 | this.setMode(prevMode); |
||
166 | }, |
||
167 | |||
168 | resize: function() { |
||
169 | this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length)); |
||
170 | |||
171 | var canvasWidth, canvasHeight; |
||
172 | var widths = [ |
||
173 | this.$el.width(), |
||
174 | DrawingBoard.Utils.boxBorderWidth(this.$el), |
||
175 | DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true) |
||
176 | ]; |
||
177 | var heights = [ |
||
178 | this.$el.height(), |
||
179 | DrawingBoard.Utils.boxBorderHeight(this.$el), |
||
180 | this.dom.$controls.height(), |
||
181 | DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true), |
||
182 | DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true) |
||
183 | ]; |
||
184 | var that = this; |
||
185 | var sum = function(values, multiplier) { //make the sum of all array values |
||
186 | multiplier = multiplier || 1; |
||
187 | var res = values[0]; |
||
188 | for (var i = 1; i < values.length; i++) { |
||
189 | res = res + (values[i]*multiplier); |
||
190 | } |
||
191 | return res; |
||
192 | }; |
||
193 | var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one |
||
194 | |||
195 | if (this.opts.enlargeYourContainer) { |
||
196 | canvasWidth = this.$el.width(); |
||
197 | canvasHeight = this.$el.height(); |
||
198 | |||
199 | this.$el.width( sum(widths) ); |
||
200 | this.$el.height( sum(heights) ); |
||
201 | } else { |
||
202 | canvasWidth = sub(widths); |
||
203 | canvasHeight = sub(heights); |
||
204 | } |
||
205 | |||
206 | this.dom.$canvasWrapper.css('width', canvasWidth + 'px'); |
||
207 | this.dom.$canvasWrapper.css('height', canvasHeight + 'px'); |
||
208 | |||
209 | this.dom.$canvas.css('width', canvasWidth + 'px'); |
||
210 | this.dom.$canvas.css('height', canvasHeight + 'px'); |
||
211 | |||
212 | this.canvas.width = canvasWidth; |
||
213 | this.canvas.height = canvasHeight; |
||
214 | }, |
||
215 | |||
216 | |||
217 | |||
218 | /** |
||
219 | * Controls: |
||
220 | * the drawing board can has various UI elements to control it. |
||
221 | * one control is represented by a class in the namespace DrawingBoard.Control |
||
222 | * it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization. |
||
223 | * |
||
224 | */ |
||
225 | |||
226 | initControls: function() { |
||
227 | this.controls = []; |
||
228 | if (!this.opts.controls.length || !DrawingBoard.Control) return false; |
||
229 | for (var i = 0; i < this.opts.controls.length; i++) { |
||
230 | var c = null; |
||
231 | if (typeof this.opts.controls[i] == "string") |
||
232 | c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this); |
||
233 | else if (typeof this.opts.controls[i] == "object") { |
||
234 | for (var controlName in this.opts.controls[i]) break; |
||
235 | c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]); |
||
236 | } |
||
237 | if (c) { |
||
238 | this.addControl(c); |
||
239 | } |
||
240 | } |
||
241 | }, |
||
242 | |||
243 | //add a new control or an existing one at the position you want in the UI |
||
244 | //to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }" |
||
245 | //the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at |
||
246 | addControl: function(control, optsOrPos, pos) { |
||
247 | if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control)) |
||
248 | return false; |
||
249 | |||
250 | var opts = typeof optsOrPos == "object" ? optsOrPos : {}; |
||
251 | pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null); |
||
252 | |||
253 | if (typeof control == "string") |
||
254 | control = new window['DrawingBoard']['Control'][control](this, opts); |
||
255 | |||
256 | if (pos) |
||
257 | this.dom.$controls.children().eq(pos).before(control.$el); |
||
258 | else |
||
259 | this.dom.$controls.append(control.$el); |
||
260 | |||
261 | if (!this.controls) |
||
262 | this.controls = []; |
||
263 | this.controls.push(control); |
||
264 | this.dom.$controls.removeClass('drawing-board-controls-hidden'); |
||
265 | }, |
||
266 | |||
267 | |||
268 | |||
269 | /** |
||
270 | * History methods: undo and redo drawed lines |
||
271 | */ |
||
272 | |||
273 | initHistory: function() { |
||
274 | this.history = new SimpleUndo({ |
||
275 | maxLength: 30, |
||
276 | provider: $.proxy(function(done) { |
||
277 | done(this.getImg()); |
||
278 | }, this), |
||
279 | onUpdate: $.proxy(function() { |
||
280 | this.ev.trigger('historyNavigation'); |
||
281 | }, this) |
||
282 | }); |
||
283 | }, |
||
284 | |||
285 | saveHistory: function() { |
||
286 | this.history.save(); |
||
287 | }, |
||
288 | |||
289 | restoreHistory: function(image) { |
||
290 | this.setImg(image, { |
||
291 | callback: $.proxy(function() { |
||
292 | this.saveWebStorage(); |
||
293 | }, this) |
||
294 | }); |
||
295 | }, |
||
296 | |||
297 | goBackInHistory: function() { |
||
298 | this.history.undo($.proxy(this.restoreHistory, this)); |
||
299 | }, |
||
300 | |||
301 | goForthInHistory: function() { |
||
302 | this.history.redo($.proxy(this.restoreHistory, this)); |
||
303 | }, |
||
304 | |||
305 | /** |
||
306 | * Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download |
||
307 | */ |
||
308 | |||
309 | setImg: function(src, opts) { |
||
310 | opts = $.extend({ |
||
311 | stretch: this.opts.stretchImg, |
||
312 | callback: null |
||
313 | }, opts); |
||
314 | |||
315 | var ctx = this.ctx; |
||
316 | var img = new Image(); |
||
317 | var oldGCO = ctx.globalCompositeOperation; |
||
318 | img.onload = function() { |
||
319 | ctx.globalCompositeOperation = "source-over"; |
||
320 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
||
321 | |||
322 | if (opts.stretch) { |
||
323 | ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height); |
||
324 | } else { |
||
325 | ctx.drawImage(img, 0, 0); |
||
326 | } |
||
327 | |||
328 | ctx.globalCompositeOperation = oldGCO; |
||
329 | |||
330 | if (opts.callback) { |
||
331 | opts.callback(); |
||
332 | } |
||
333 | }; |
||
334 | img.src = src; |
||
335 | }, |
||
336 | |||
337 | getImg: function() { |
||
338 | return this.canvas.toDataURL("image/png"); |
||
339 | }, |
||
340 | |||
341 | downloadImg: function() { |
||
342 | var img = this.getImg(); |
||
343 | img = img.replace("image/png", "image/octet-stream"); |
||
344 | window.location.href = img; |
||
345 | }, |
||
346 | |||
347 | |||
348 | |||
349 | /** |
||
350 | * WebStorage handling : save and restore to local or session storage |
||
351 | */ |
||
352 | |||
353 | saveWebStorage: function() { |
||
354 | if (window[this.storage]) { |
||
355 | window[this.storage].setItem('drawing-board-' + this.id, this.getImg()); |
||
356 | this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg()); |
||
357 | } |
||
358 | }, |
||
359 | |||
360 | restoreWebStorage: function() { |
||
361 | if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { |
||
362 | this.setImg(window[this.storage].getItem('drawing-board-' + this.id)); |
||
363 | this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id)); |
||
364 | } |
||
365 | }, |
||
366 | |||
367 | clearWebStorage: function() { |
||
368 | if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { |
||
369 | window[this.storage].removeItem('drawing-board-' + this.id); |
||
370 | this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1)); |
||
371 | } |
||
372 | }, |
||
373 | |||
374 | _getStorage: function() { |
||
375 | if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false; |
||
376 | return this.opts.webStorage + 'Storage'; |
||
377 | }, |
||
378 | |||
379 | |||
380 | |||
381 | /** |
||
382 | * Drop an image on the canvas to draw on it |
||
383 | */ |
||
384 | |||
385 | initDropEvents: function() { |
||
386 | if (!this.opts.droppable) |
||
387 | return false; |
||
388 | |||
389 | this.dom.$canvas.on('dragover dragenter drop', function(e) { |
||
390 | e.stopPropagation(); |
||
391 | e.preventDefault(); |
||
392 | }); |
||
393 | |||
394 | this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this)); |
||
395 | }, |
||
396 | |||
397 | _onCanvasDrop: function(e) { |
||
398 | e = e.originalEvent ? e.originalEvent : e; |
||
399 | var files = e.dataTransfer.files; |
||
400 | if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader) |
||
401 | return false; |
||
402 | var fr = new FileReader(); |
||
403 | fr.readAsDataURL(files[0]); |
||
404 | fr.onload = $.proxy(function(ev) { |
||
405 | this.setImg(ev.target.result, { |
||
406 | callback: $.proxy(function() { |
||
407 | this.saveHistory(); |
||
408 | }, this) |
||
409 | }); |
||
410 | this.ev.trigger('board:imageDropped', ev.target.result); |
||
411 | this.ev.trigger('board:userAction'); |
||
412 | }, this); |
||
413 | }, |
||
414 | |||
415 | |||
416 | |||
417 | /** |
||
418 | * set and get current drawing mode |
||
419 | * |
||
420 | * possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can) |
||
421 | */ |
||
422 | |||
423 | setMode: function(newMode, silent) { |
||
424 | silent = silent || false; |
||
425 | newMode = newMode || 'pencil'; |
||
426 | |||
427 | this.ev.unbind('board:startDrawing', $.proxy(this.fill, this)); |
||
428 | |||
429 | if (this.opts.eraserColor === "transparent") |
||
430 | this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over"; |
||
431 | else { |
||
432 | if (newMode === "eraser") { |
||
433 | if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background)) |
||
434 | this.ctx.strokeStyle = this.opts.background; |
||
435 | else if (DrawingBoard.Utils.isColor(this.opts.eraserColor)) |
||
436 | this.ctx.strokeStyle = this.opts.eraserColor; |
||
437 | } else if (!this.mode || this.mode === "eraser") { |
||
438 | this.ctx.strokeStyle = this.color; |
||
439 | } |
||
440 | |||
441 | if (newMode === "filler") |
||
442 | this.ev.bind('board:startDrawing', $.proxy(this.fill, this)); |
||
443 | } |
||
444 | this.mode = newMode; |
||
445 | if (!silent) |
||
446 | this.ev.trigger('board:mode', this.mode); |
||
447 | }, |
||
448 | |||
449 | getMode: function() { |
||
450 | return this.mode || "pencil"; |
||
451 | }, |
||
452 | |||
453 | setColor: function(color) { |
||
454 | var that = this; |
||
455 | color = color || this.color; |
||
456 | if (!DrawingBoard.Utils.isColor(color)) |
||
457 | return false; |
||
458 | this.color = color; |
||
459 | if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") { |
||
460 | var setStrokeStyle = function(mode) { |
||
461 | if (mode !== "eraser") |
||
462 | that.strokeStyle = that.color; |
||
463 | that.ev.unbind('board:mode', setStrokeStyle); |
||
464 | }; |
||
465 | this.ev.bind('board:mode', setStrokeStyle); |
||
466 | } else |
||
467 | this.ctx.strokeStyle = this.color; |
||
468 | }, |
||
469 | |||
470 | /** |
||
471 | * Fills an area with the current stroke color. |
||
472 | */ |
||
473 | fill: function(e) { |
||
474 | if (this.getImg() === this.blankCanvas) { |
||
475 | this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); |
||
476 | this.ctx.fillStyle = this.color; |
||
477 | this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); |
||
478 | return; |
||
479 | } |
||
480 | |||
481 | var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); |
||
482 | |||
483 | // constants identifying pixels components |
||
484 | var INDEX = 0, X = 1, Y = 2, COLOR = 3; |
||
485 | |||
486 | // target color components |
||
487 | var stroke = this.ctx.strokeStyle; |
||
488 | var r = parseInt(stroke.substr(1, 2), 16); |
||
489 | var g = parseInt(stroke.substr(3, 2), 16); |
||
490 | var b = parseInt(stroke.substr(5, 2), 16); |
||
491 | |||
492 | // starting point |
||
493 | var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10)); |
||
494 | var startColor = start[COLOR]; |
||
495 | var tolerance = this.opts.fillTolerance; |
||
496 | var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38 |
||
497 | |||
498 | // no need to continue if starting and target colors are the same |
||
499 | if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance)) |
||
500 | return; |
||
501 | |||
502 | // pixels to evaluate |
||
503 | var queue = [start]; |
||
504 | |||
505 | // loop vars |
||
506 | var pixel, x, y; |
||
507 | var maxX = img.width - 1; |
||
508 | var maxY = img.height - 1; |
||
509 | |||
510 | function updatePixelColor(pixel) { |
||
511 | img.data[pixel[INDEX]] = r; |
||
512 | img.data[pixel[INDEX] + 1] = g; |
||
513 | img.data[pixel[INDEX] + 2] = b; |
||
514 | } |
||
515 | |||
516 | while ((pixel = queue.pop())) { |
||
517 | if (useHack) |
||
518 | updatePixelColor(pixel); |
||
519 | |||
520 | if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) { |
||
521 | if (!useHack) |
||
522 | updatePixelColor(pixel); |
||
523 | if (pixel[X] > 0) // west |
||
524 | queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y])); |
||
525 | if (pixel[X] < maxX) // east |
||
526 | queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y])); |
||
527 | if (pixel[Y] > 0) // north |
||
528 | queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1)); |
||
529 | if (pixel[Y] < maxY) // south |
||
530 | queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1)); |
||
531 | } |
||
532 | } |
||
533 | |||
534 | this.ctx.putImageData(img, 0, 0); |
||
535 | }, |
||
536 | |||
537 | |||
538 | /** |
||
539 | * Drawing handling, with mouse or touch |
||
540 | */ |
||
541 | |||
542 | initDrawEvents: function() { |
||
543 | this.isDrawing = false; |
||
544 | this.isMouseHovering = false; |
||
545 | this.coords = {}; |
||
546 | this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 }; |
||
547 | |||
548 | this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) { |
||
549 | this._onInputStart(e, this._getInputCoords(e) ); |
||
550 | }, this)); |
||
551 | |||
552 | this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) { |
||
553 | this._onInputMove(e, this._getInputCoords(e) ); |
||
554 | }, this)); |
||
555 | |||
556 | this.dom.$canvas.on('mousemove', $.proxy(function(e) { |
||
557 | |||
558 | }, this)); |
||
559 | |||
560 | this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) { |
||
561 | this._onInputStop(e, this._getInputCoords(e) ); |
||
562 | }, this)); |
||
563 | |||
564 | this.dom.$canvas.on('mouseover', $.proxy(function(e) { |
||
565 | this._onMouseOver(e, this._getInputCoords(e) ); |
||
566 | }, this)); |
||
567 | |||
568 | this.dom.$canvas.on('mouseout', $.proxy(function(e) { |
||
569 | this._onMouseOut(e, this._getInputCoords(e) ); |
||
570 | |||
571 | }, this)); |
||
572 | |||
573 | $('body').on('mouseup touchend', $.proxy(function(e) { |
||
574 | this.isDrawing = false; |
||
575 | }, this)); |
||
576 | |||
577 | if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) ); |
||
578 | }, |
||
579 | |||
580 | draw: function() { |
||
581 | //if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil |
||
582 | //todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported |
||
583 | //we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad. |
||
584 | if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) { |
||
585 | this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' }); |
||
586 | var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) }); |
||
587 | this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform }); |
||
588 | this.dom.$cursor.removeClass('drawing-board-utils-hidden'); |
||
589 | } else { |
||
590 | this.dom.$cursor.addClass('drawing-board-utils-hidden'); |
||
591 | } |
||
592 | |||
593 | if (this.isDrawing) { |
||
594 | var currentMid = this._getMidInputCoords(this.coords.current); |
||
595 | this.ctx.beginPath(); |
||
596 | this.ctx.moveTo(currentMid.x, currentMid.y); |
||
597 | this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y); |
||
598 | this.ctx.stroke(); |
||
599 | |||
600 | this.coords.old = this.coords.current; |
||
601 | this.coords.oldMid = currentMid; |
||
602 | } |
||
603 | |||
604 | if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) ); |
||
605 | }, |
||
606 | |||
607 | _onInputStart: function(e, coords) { |
||
608 | this.coords.current = this.coords.old = coords; |
||
609 | this.coords.oldMid = this._getMidInputCoords(coords); |
||
610 | this.isDrawing = true; |
||
611 | |||
612 | if (!window.requestAnimationFrame) this.draw(); |
||
613 | |||
614 | this.ev.trigger('board:startDrawing', {e: e, coords: coords}); |
||
615 | e.stopPropagation(); |
||
616 | e.preventDefault(); |
||
617 | }, |
||
618 | |||
619 | _onInputMove: function(e, coords) { |
||
620 | this.coords.current = coords; |
||
621 | this.ev.trigger('board:drawing', {e: e, coords: coords}); |
||
622 | |||
623 | if (!window.requestAnimationFrame) this.draw(); |
||
624 | |||
625 | e.stopPropagation(); |
||
626 | e.preventDefault(); |
||
627 | }, |
||
628 | |||
629 | _onInputStop: function(e, coords) { |
||
630 | if (this.isDrawing && (!e.touches || e.touches.length === 0)) { |
||
631 | this.isDrawing = false; |
||
632 | |||
633 | this.saveWebStorage(); |
||
634 | this.saveHistory(); |
||
635 | |||
636 | this.ev.trigger('board:stopDrawing', {e: e, coords: coords}); |
||
637 | this.ev.trigger('board:userAction'); |
||
638 | e.stopPropagation(); |
||
639 | e.preventDefault(); |
||
640 | } |
||
641 | }, |
||
642 | |||
643 | _onMouseOver: function(e, coords) { |
||
644 | this.isMouseHovering = true; |
||
645 | this.coords.old = this._getInputCoords(e); |
||
646 | this.coords.oldMid = this._getMidInputCoords(this.coords.old); |
||
647 | |||
648 | this.ev.trigger('board:mouseOver', {e: e, coords: coords}); |
||
649 | }, |
||
650 | |||
651 | _onMouseOut: function(e, coords) { |
||
652 | this.isMouseHovering = false; |
||
653 | |||
654 | this.ev.trigger('board:mouseOut', {e: e, coords: coords}); |
||
655 | }, |
||
656 | |||
657 | _getInputCoords: function(e) { |
||
658 | e = e.originalEvent ? e.originalEvent : e; |
||
659 | var |
||
660 | rect = this.canvas.getBoundingClientRect(), |
||
661 | width = this.dom.$canvas.width(), |
||
662 | height = this.dom.$canvas.height() |
||
663 | ; |
||
664 | var x, y; |
||
665 | if (e.touches && e.touches.length == 1) { |
||
666 | x = e.touches[0].pageX; |
||
667 | y = e.touches[0].pageY; |
||
668 | } else { |
||
669 | x = e.pageX; |
||
670 | y = e.pageY; |
||
671 | } |
||
672 | x = x - this.dom.$canvas.offset().left; |
||
673 | y = y - this.dom.$canvas.offset().top; |
||
674 | x *= (width / rect.width); |
||
675 | y *= (height / rect.height); |
||
676 | return { |
||
677 | x: x, |
||
678 | y: y |
||
679 | }; |
||
680 | }, |
||
681 | |||
682 | _getMidInputCoords: function(coords) { |
||
683 | return { |
||
684 | x: this.coords.old.x + coords.x>>1, |
||
685 | y: this.coords.old.y + coords.y>>1 |
||
686 | }; |
||
687 | } |
||
688 | }; |