scratch – Blame information for rev 84

Subversion Repositories:
Rev:
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 };