scratch – Blame information for rev 84

Subversion Repositories:
Rev:
Rev Author Line No. Line
84 office 1 /* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
2 * Copyright (c) 2015 Emmanuel Pelletier
3 * Licensed MIT */
4 (function() {
5  
6 'use strict';
7  
8 /**
9 * SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
10 *
11 * options are: {
12 * * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
13 * * `maxLength` : the maximum number of items in history
14 * * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
15 * }
16 *
17 */
18 var SimpleUndo = function(options) {
19  
20 var settings = options ? options : {};
21 var defaultOptions = {
22 provider: function() {
23 throw new Error("No provider!");
24 },
25 maxLength: 30,
26 onUpdate: function() {}
27 };
28  
29 this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
30 this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
31 this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
32  
33 this.initialItem = null;
34 this.clear();
35 };
36  
37 function truncate (stack, limit) {
38 while (stack.length > limit) {
39 stack.shift();
40 }
41 }
42  
43 SimpleUndo.prototype.initialize = function(initialItem) {
44 this.stack[0] = initialItem;
45 this.initialItem = initialItem;
46 };
47  
48  
49 SimpleUndo.prototype.clear = function() {
50 this.stack = [this.initialItem];
51 this.position = 0;
52 this.onUpdate();
53 };
54  
55 SimpleUndo.prototype.save = function() {
56 this.provider(function(current) {
57 truncate(this.stack, this.maxLength);
58 this.position = Math.min(this.position,this.stack.length - 1);
59  
60 this.stack = this.stack.slice(0, this.position + 1);
61 this.stack.push(current);
62 this.position++;
63 this.onUpdate();
64 }.bind(this));
65 };
66  
67 SimpleUndo.prototype.undo = function(callback) {
68 if (this.canUndo()) {
69 var item = this.stack[--this.position];
70 this.onUpdate();
71  
72 if (callback) {
73 callback(item);
74 }
75 }
76 };
77  
78 SimpleUndo.prototype.redo = function(callback) {
79 if (this.canRedo()) {
80 var item = this.stack[++this.position];
81 this.onUpdate();
82  
83 if (callback) {
84 callback(item);
85 }
86 }
87 };
88  
89 SimpleUndo.prototype.canUndo = function() {
90 return this.position > 0;
91 };
92  
93 SimpleUndo.prototype.canRedo = function() {
94 return this.position < this.count();
95 };
96  
97 SimpleUndo.prototype.count = function() {
98 return this.stack.length - 1; // -1 because of initial item
99 };
100  
101  
102  
103  
104  
105 //exports
106 // node module
107 if (typeof module != 'undefined') {
108 module.exports = SimpleUndo;
109 }
110  
111 // browser global
112 if (typeof window != 'undefined') {
113 window.SimpleUndo = SimpleUndo;
114 }
115  
116 })();
117 window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
118  
119  
120 DrawingBoard.Utils = {};
121  
122 /*!
123 * Tim (lite)
124 * github.com/premasagar/tim
125 *//*
126 A tiny, secure JavaScript micro-templating script.
127 */
128 DrawingBoard.Utils.tpl = (function(){
129 "use strict";
130  
131 var start = "{{",
132 end = "}}",
133 path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
134 pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
135 undef;
136  
137 return function(template, data){
138 // Merge data into the template string
139 return template.replace(pattern, function(tag, token){
140 var path = token.split("."),
141 len = path.length,
142 lookup = data,
143 i = 0;
144  
145 for (; i < len; i++){
146 lookup = lookup[path[i]];
147  
148 // Property not found
149 if (lookup === undef){
150 throw "tim: '" + path[i] + "' not found in " + tag;
151 }
152  
153 // Return the required value
154 if (i === len - 1){
155 return lookup;
156 }
157 }
158 });
159 };
160 }());
161  
162 /**
163 * https://github.com/jeromeetienne/microevent.js
164 * MicroEvent - to make any js object an event emitter (server or browser)
165 *
166 * - pure javascript - server compatible, browser compatible
167 * - dont rely on the browser doms
168 * - super simple - you get it immediatly, no mistery, no magic involved
169 *
170 * - create a MicroEventDebug with goodies to debug
171 * - make it safer to use
172 */
173 DrawingBoard.Utils.MicroEvent = function(){};
174  
175 DrawingBoard.Utils.MicroEvent.prototype = {
176 bind : function(event, fct){
177 this._events = this._events || {};
178 this._events[event] = this._events[event] || [];
179 this._events[event].push(fct);
180 },
181 unbind : function(event, fct){
182 this._events = this._events || {};
183 if( event in this._events === false ) return;
184 this._events[event].splice(this._events[event].indexOf(fct), 1);
185 },
186 trigger : function(event /* , args... */){
187 this._events = this._events || {};
188 if( event in this._events === false ) return;
189 for(var i = 0; i < this._events[event].length; i++){
190 this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
191 }
192 }
193 };
194  
195 //I know.
196 DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
197 withPadding = !!withPadding || true;
198 withMargin = !!withMargin || false;
199 var width = 0,
200 props;
201 if (direction == "width") {
202 props = ['border-left-width', 'border-right-width'];
203 if (withPadding) props.push('padding-left', 'padding-right');
204 if (withMargin) props.push('margin-left', 'margin-right');
205 } else {
206 props = ['border-top-width', 'border-bottom-width'];
207 if (withPadding) props.push('padding-top', 'padding-bottom');
208 if (withMargin) props.push('margin-top', 'margin-bottom');
209 }
210 for (var i = props.length - 1; i >= 0; i--)
211 width += parseInt($el.css(props[i]).replace('px', ''), 10);
212 return width;
213 };
214  
215 DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
216 return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
217 };
218  
219 DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
220 return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
221 };
222  
223 DrawingBoard.Utils.isColor = function(string) {
224 if (!string || !string.length) return false;
225 return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
226 };
227  
228 /**
229 * Packs an RGB color into a single integer.
230 */
231 DrawingBoard.Utils.RGBToInt = function(r, g, b) {
232 var c = 0;
233 c |= (r & 255) << 16;
234 c |= (g & 255) << 8;
235 c |= (b & 255);
236 return c;
237 };
238  
239 /**
240 * Returns informations on the pixel located at (x,y).
241 */
242 DrawingBoard.Utils.pixelAt = function(image, x, y) {
243 var i = (y * image.width + x) * 4;
244 var c = DrawingBoard.Utils.RGBToInt(
245 image.data[i],
246 image.data[i + 1],
247 image.data[i + 2]
248 );
249  
250 return [
251 i, // INDEX
252 x, // X
253 y, // Y
254 c // COLOR
255 ];
256 };
257  
258 /**
259 * Compares two colors with the given tolerance (between 0 and 255).
260 */
261 DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
262 if (tolerance === 0) {
263 return (a === b);
264 }
265  
266 var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
267 ga = (a >> 8) & 255, gb = (b >> 8) & 255,
268 ba = a & 255, bb = b & 255;
269  
270 return (Math.abs(ra - rb) <= tolerance)
271 && (Math.abs(ga - gb) <= tolerance)
272 && (Math.abs(ba - bb) <= tolerance);
273 };
274  
275 (function() {
276 var lastTime = 0;
277 var vendors = ['ms', 'moz', 'webkit', 'o'];
278 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
279 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
280 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
281 }
282 }());
283  
284 window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
285  
286 /**
287 * pass the id of the html element to put the drawing board into
288 * and some options : {
289 * controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
290 * instead of simple strings, you can pass an object to define a control opts
291 * ie ['Color', { Navigation: { reset: false }}]
292 * controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
293 * background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
294 * color: pencil color ("#000000" by default)
295 * size: pencil size (3 by default)
296 * webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
297 * 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,
298 * errorMessage: html string to put in the board's element on browsers that don't support canvas.
299 * stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default
300 * }
301 */
302 DrawingBoard.Board = function(id, opts) {
303 this.opts = this.mergeOptions(opts);
304  
305 this.ev = new DrawingBoard.Utils.MicroEvent();
306  
307 this.id = id;
308 this.$el = $(document.getElementById(id));
309 if (!this.$el.length)
310 return false;
311  
312 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>';
313 if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
314 else tpl = '<div class="drawing-board-controls"></div>' + tpl;
315  
316 this.$el.addClass('drawing-board').append(tpl);
317 this.dom = {
318 $canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
319 $canvas: this.$el.find('.drawing-board-canvas'),
320 $cursor: this.$el.find('.drawing-board-cursor'),
321 $controls: this.$el.find('.drawing-board-controls')
322 };
323  
324 $.each(['left', 'right', 'center'], $.proxy(function(n, val) {
325 if (this.opts.controlsPosition.indexOf(val) > -1) {
326 this.dom.$controls.attr('data-align', val);
327 return false;
328 }
329 }, this));
330  
331 this.canvas = this.dom.$canvas.get(0);
332 this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
333 this.color = this.opts.color;
334  
335 if (!this.ctx) {
336 if (this.opts.errorMessage)
337 this.$el.html(this.opts.errorMessage);
338 return false;
339 }
340  
341 this.storage = this._getStorage();
342  
343 this.initHistory();
344 //init default board values before controls are added (mostly pencil color and size)
345 this.reset({ webStorage: false, history: false, background: false });
346 //init controls (they will need the default board values to work like pencil color and size)
347 this.initControls();
348 //set board's size after the controls div is added
349 this.resize();
350 //reset the board to take all resized space
351 this.reset({ webStorage: false, history: false, background: true });
352 this.restoreWebStorage();
353 this.initDropEvents();
354 this.initDrawEvents();
355 };
356  
357  
358  
359 DrawingBoard.Board.defaultOpts = {
360 controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
361 controlsPosition: "top left",
362 color: "#000000",
363 size: 1,
364 background: "#fff",
365 eraserColor: "background",
366 fillTolerance: 100,
367 fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
368 webStorage: 'session',
369 droppable: false,
370 enlargeYourContainer: false,
371 errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
372 stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true
373 };
374  
375  
376  
377 DrawingBoard.Board.prototype = {
378  
379 mergeOptions: function(opts) {
380 opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
381 if (!opts.background && opts.eraserColor === "background")
382 opts.eraserColor = "transparent";
383 return opts;
384 },
385  
386 /**
387 * Canvas reset/resize methods: put back the canvas to its default values
388 *
389 * depending on options, can set color, size, background back to default values
390 * and store the reseted canvas in webstorage and history queue
391 *
392 * resize values depend on the `enlargeYourContainer` option
393 */
394  
395 reset: function(opts) {
396 opts = $.extend({
397 color: this.opts.color,
398 size: this.opts.size,
399 webStorage: true,
400 history: true,
401 background: false
402 }, opts);
403  
404 this.setMode('pencil');
405  
406 if (opts.background) {
407 this.resetBackground(this.opts.background, $.proxy(function() {
408 if (opts.history) this.saveHistory();
409 }, this));
410 }
411  
412 if (opts.color) this.setColor(opts.color);
413 if (opts.size) this.ctx.lineWidth = opts.size;
414  
415 this.ctx.lineCap = "round";
416 this.ctx.lineJoin = "round";
417 // this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
418  
419 if (opts.webStorage) this.saveWebStorage();
420  
421 // if opts.background we already dealt with the history
422 if (opts.history && !opts.background) this.saveHistory();
423  
424 this.blankCanvas = this.getImg();
425  
426 this.ev.trigger('board:reset', opts);
427 },
428  
429 resetBackground: function(background, callback) {
430 background = background || this.opts.background;
431  
432 var bgIsColor = DrawingBoard.Utils.isColor(background);
433 var prevMode = this.getMode();
434 this.setMode('pencil');
435 this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
436 if (bgIsColor) {
437 this.ctx.fillStyle = background;
438 this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
439 this.history.initialize(this.getImg());
440 if (callback) callback();
441 } else if (background)
442 this.setImg(background, {
443 callback: $.proxy(function() {
444 this.history.initialize(this.getImg());
445 if (callback) callback();
446 }, this)
447 });
448 this.setMode(prevMode);
449 },
450  
451 resize: function() {
452 this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
453  
454 var canvasWidth, canvasHeight;
455 var widths = [
456 this.$el.width(),
457 DrawingBoard.Utils.boxBorderWidth(this.$el),
458 DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
459 ];
460 var heights = [
461 this.$el.height(),
462 DrawingBoard.Utils.boxBorderHeight(this.$el),
463 this.dom.$controls.height(),
464 DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
465 DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
466 ];
467 var that = this;
468 var sum = function(values, multiplier) { //make the sum of all array values
469 multiplier = multiplier || 1;
470 var res = values[0];
471 for (var i = 1; i < values.length; i++) {
472 res = res + (values[i]*multiplier);
473 }
474 return res;
475 };
476 var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
477  
478 if (this.opts.enlargeYourContainer) {
479 canvasWidth = this.$el.width();
480 canvasHeight = this.$el.height();
481  
482 this.$el.width( sum(widths) );
483 this.$el.height( sum(heights) );
484 } else {
485 canvasWidth = sub(widths);
486 canvasHeight = sub(heights);
487 }
488  
489 this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
490 this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
491  
492 this.dom.$canvas.css('width', canvasWidth + 'px');
493 this.dom.$canvas.css('height', canvasHeight + 'px');
494  
495 this.canvas.width = canvasWidth;
496 this.canvas.height = canvasHeight;
497 },
498  
499  
500  
501 /**
502 * Controls:
503 * the drawing board can has various UI elements to control it.
504 * one control is represented by a class in the namespace DrawingBoard.Control
505 * it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
506 *
507 */
508  
509 initControls: function() {
510 this.controls = [];
511 if (!this.opts.controls.length || !DrawingBoard.Control) return false;
512 for (var i = 0; i < this.opts.controls.length; i++) {
513 var c = null;
514 if (typeof this.opts.controls[i] == "string")
515 c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
516 else if (typeof this.opts.controls[i] == "object") {
517 for (var controlName in this.opts.controls[i]) break;
518 c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
519 }
520 if (c) {
521 this.addControl(c);
522 }
523 }
524 },
525  
526 //add a new control or an existing one at the position you want in the UI
527 //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 }"
528 //the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
529 addControl: function(control, optsOrPos, pos) {
530 if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
531 return false;
532  
533 var opts = typeof optsOrPos == "object" ? optsOrPos : {};
534 pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
535  
536 if (typeof control == "string")
537 control = new window['DrawingBoard']['Control'][control](this, opts);
538  
539 if (pos)
540 this.dom.$controls.children().eq(pos).before(control.$el);
541 else
542 this.dom.$controls.append(control.$el);
543  
544 if (!this.controls)
545 this.controls = [];
546 this.controls.push(control);
547 this.dom.$controls.removeClass('drawing-board-controls-hidden');
548 },
549  
550  
551  
552 /**
553 * History methods: undo and redo drawed lines
554 */
555  
556 initHistory: function() {
557 this.history = new SimpleUndo({
558 maxLength: 30,
559 provider: $.proxy(function(done) {
560 done(this.getImg());
561 }, this),
562 onUpdate: $.proxy(function() {
563 this.ev.trigger('historyNavigation');
564 }, this)
565 });
566 },
567  
568 saveHistory: function() {
569 this.history.save();
570 },
571  
572 restoreHistory: function(image) {
573 this.setImg(image, {
574 callback: $.proxy(function() {
575 this.saveWebStorage();
576 }, this)
577 });
578 },
579  
580 goBackInHistory: function() {
581 this.history.undo($.proxy(this.restoreHistory, this));
582 },
583  
584 goForthInHistory: function() {
585 this.history.redo($.proxy(this.restoreHistory, this));
586 },
587  
588 /**
589 * Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
590 */
591  
592 setImg: function(src, opts) {
593 opts = $.extend({
594 stretch: this.opts.stretchImg,
595 callback: null
596 }, opts);
597  
598 var ctx = this.ctx;
599 var img = new Image();
600 var oldGCO = ctx.globalCompositeOperation;
601 img.onload = function() {
602 ctx.globalCompositeOperation = "source-over";
603 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
604  
605 if (opts.stretch) {
606 ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
607 } else {
608 ctx.drawImage(img, 0, 0);
609 }
610  
611 ctx.globalCompositeOperation = oldGCO;
612  
613 if (opts.callback) {
614 opts.callback();
615 }
616 };
617 img.src = src;
618 },
619  
620 getImg: function() {
621 return this.canvas.toDataURL("image/png");
622 },
623  
624 downloadImg: function() {
625 var img = this.getImg();
626 img = img.replace("image/png", "image/octet-stream");
627 window.location.href = img;
628 },
629  
630  
631  
632 /**
633 * WebStorage handling : save and restore to local or session storage
634 */
635  
636 saveWebStorage: function() {
637 if (window[this.storage]) {
638 window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
639 this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
640 }
641 },
642  
643 restoreWebStorage: function() {
644 if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
645 this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
646 this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
647 }
648 },
649  
650 clearWebStorage: function() {
651 if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
652 window[this.storage].removeItem('drawing-board-' + this.id);
653 this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
654 }
655 },
656  
657 _getStorage: function() {
658 if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
659 return this.opts.webStorage + 'Storage';
660 },
661  
662  
663  
664 /**
665 * Drop an image on the canvas to draw on it
666 */
667  
668 initDropEvents: function() {
669 if (!this.opts.droppable)
670 return false;
671  
672 this.dom.$canvas.on('dragover dragenter drop', function(e) {
673 e.stopPropagation();
674 e.preventDefault();
675 });
676  
677 this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
678 },
679  
680 _onCanvasDrop: function(e) {
681 e = e.originalEvent ? e.originalEvent : e;
682 var files = e.dataTransfer.files;
683 if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
684 return false;
685 var fr = new FileReader();
686 fr.readAsDataURL(files[0]);
687 fr.onload = $.proxy(function(ev) {
688 this.setImg(ev.target.result, {
689 callback: $.proxy(function() {
690 this.saveHistory();
691 }, this)
692 });
693 this.ev.trigger('board:imageDropped', ev.target.result);
694 this.ev.trigger('board:userAction');
695 }, this);
696 },
697  
698  
699  
700 /**
701 * set and get current drawing mode
702 *
703 * possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
704 */
705  
706 setMode: function(newMode, silent) {
707 silent = silent || false;
708 newMode = newMode || 'pencil';
709  
710 this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
711  
712 if (this.opts.eraserColor === "transparent")
713 this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
714 else {
715 if (newMode === "eraser") {
716 if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
717 this.ctx.strokeStyle = this.opts.background;
718 else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
719 this.ctx.strokeStyle = this.opts.eraserColor;
720 } else if (!this.mode || this.mode === "eraser") {
721 this.ctx.strokeStyle = this.color;
722 }
723  
724 if (newMode === "filler")
725 this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
726 }
727 this.mode = newMode;
728 if (!silent)
729 this.ev.trigger('board:mode', this.mode);
730 },
731  
732 getMode: function() {
733 return this.mode || "pencil";
734 },
735  
736 setColor: function(color) {
737 var that = this;
738 color = color || this.color;
739 if (!DrawingBoard.Utils.isColor(color))
740 return false;
741 this.color = color;
742 if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
743 var setStrokeStyle = function(mode) {
744 if (mode !== "eraser")
745 that.strokeStyle = that.color;
746 that.ev.unbind('board:mode', setStrokeStyle);
747 };
748 this.ev.bind('board:mode', setStrokeStyle);
749 } else
750 this.ctx.strokeStyle = this.color;
751 },
752  
753 /**
754 * Fills an area with the current stroke color.
755 */
756 fill: function(e) {
757 if (this.getImg() === this.blankCanvas) {
758 this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
759 this.ctx.fillStyle = this.color;
760 this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
761 return;
762 }
763  
764 var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
765  
766 // constants identifying pixels components
767 var INDEX = 0, X = 1, Y = 2, COLOR = 3;
768  
769 // target color components
770 var stroke = this.ctx.strokeStyle;
771 var r = parseInt(stroke.substr(1, 2), 16);
772 var g = parseInt(stroke.substr(3, 2), 16);
773 var b = parseInt(stroke.substr(5, 2), 16);
774  
775 // starting point
776 var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10));
777 var startColor = start[COLOR];
778 var tolerance = this.opts.fillTolerance;
779 var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38
780  
781 // no need to continue if starting and target colors are the same
782 if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance))
783 return;
784  
785 // pixels to evaluate
786 var queue = [start];
787  
788 // loop vars
789 var pixel, x, y;
790 var maxX = img.width - 1;
791 var maxY = img.height - 1;
792  
793 function updatePixelColor(pixel) {
794 img.data[pixel[INDEX]] = r;
795 img.data[pixel[INDEX] + 1] = g;
796 img.data[pixel[INDEX] + 2] = b;
797 }
798  
799 while ((pixel = queue.pop())) {
800 if (useHack)
801 updatePixelColor(pixel);
802  
803 if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) {
804 if (!useHack)
805 updatePixelColor(pixel);
806 if (pixel[X] > 0) // west
807 queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
808 if (pixel[X] < maxX) // east
809 queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
810 if (pixel[Y] > 0) // north
811 queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
812 if (pixel[Y] < maxY) // south
813 queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
814 }
815 }
816  
817 this.ctx.putImageData(img, 0, 0);
818 },
819  
820  
821 /**
822 * Drawing handling, with mouse or touch
823 */
824  
825 initDrawEvents: function() {
826 this.isDrawing = false;
827 this.isMouseHovering = false;
828 this.coords = {};
829 this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
830  
831 this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
832 this._onInputStart(e, this._getInputCoords(e) );
833 }, this));
834  
835 this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
836 this._onInputMove(e, this._getInputCoords(e) );
837 }, this));
838  
839 this.dom.$canvas.on('mousemove', $.proxy(function(e) {
840  
841 }, this));
842  
843 this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
844 this._onInputStop(e, this._getInputCoords(e) );
845 }, this));
846  
847 this.dom.$canvas.on('mouseover', $.proxy(function(e) {
848 this._onMouseOver(e, this._getInputCoords(e) );
849 }, this));
850  
851 this.dom.$canvas.on('mouseout', $.proxy(function(e) {
852 this._onMouseOut(e, this._getInputCoords(e) );
853  
854 }, this));
855  
856 $('body').on('mouseup touchend', $.proxy(function(e) {
857 this.isDrawing = false;
858 }, this));
859  
860 if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
861 },
862  
863 draw: function() {
864 //if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
865 //todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
866 //we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
867 if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
868 this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
869 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) });
870 this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
871 this.dom.$cursor.removeClass('drawing-board-utils-hidden');
872 } else {
873 this.dom.$cursor.addClass('drawing-board-utils-hidden');
874 }
875  
876 if (this.isDrawing) {
877 var currentMid = this._getMidInputCoords(this.coords.current);
878 this.ctx.beginPath();
879 this.ctx.moveTo(currentMid.x, currentMid.y);
880 this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
881 this.ctx.stroke();
882  
883 this.coords.old = this.coords.current;
884 this.coords.oldMid = currentMid;
885 }
886  
887 if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
888 },
889  
890 _onInputStart: function(e, coords) {
891 this.coords.current = this.coords.old = coords;
892 this.coords.oldMid = this._getMidInputCoords(coords);
893 this.isDrawing = true;
894  
895 if (!window.requestAnimationFrame) this.draw();
896  
897 this.ev.trigger('board:startDrawing', {e: e, coords: coords});
898 e.stopPropagation();
899 e.preventDefault();
900 },
901  
902 _onInputMove: function(e, coords) {
903 this.coords.current = coords;
904 this.ev.trigger('board:drawing', {e: e, coords: coords});
905  
906 if (!window.requestAnimationFrame) this.draw();
907  
908 e.stopPropagation();
909 e.preventDefault();
910 },
911  
912 _onInputStop: function(e, coords) {
913 if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
914 this.isDrawing = false;
915  
916 this.saveWebStorage();
917 this.saveHistory();
918  
919 this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
920 this.ev.trigger('board:userAction');
921 e.stopPropagation();
922 e.preventDefault();
923 }
924 },
925  
926 _onMouseOver: function(e, coords) {
927 this.isMouseHovering = true;
928 this.coords.old = this._getInputCoords(e);
929 this.coords.oldMid = this._getMidInputCoords(this.coords.old);
930  
931 this.ev.trigger('board:mouseOver', {e: e, coords: coords});
932 },
933  
934 _onMouseOut: function(e, coords) {
935 this.isMouseHovering = false;
936  
937 this.ev.trigger('board:mouseOut', {e: e, coords: coords});
938 },
939  
940 _getInputCoords: function(e) {
941 e = e.originalEvent ? e.originalEvent : e;
942 var
943 rect = this.canvas.getBoundingClientRect(),
944 width = this.dom.$canvas.width(),
945 height = this.dom.$canvas.height()
946 ;
947 var x, y;
948 if (e.touches && e.touches.length == 1) {
949 x = e.touches[0].pageX;
950 y = e.touches[0].pageY;
951 } else {
952 x = e.pageX;
953 y = e.pageY;
954 }
955 x = x - this.dom.$canvas.offset().left;
956 y = y - this.dom.$canvas.offset().top;
957 x *= (width / rect.width);
958 y *= (height / rect.height);
959 return {
960 x: x,
961 y: y
962 };
963 },
964  
965 _getMidInputCoords: function(coords) {
966 return {
967 x: this.coords.old.x + coords.x>>1,
968 y: this.coords.old.y + coords.y>>1
969 };
970 }
971 };
972  
973 DrawingBoard.Control = function(drawingBoard, opts) {
974 this.board = drawingBoard;
975 this.opts = $.extend({}, this.defaults, opts);
976  
977 this.$el = $(document.createElement('div')).addClass('drawing-board-control');
978 if (this.name)
979 this.$el.addClass('drawing-board-control-' + this.name);
980  
981 this.board.ev.bind('board:reset', $.proxy(this.onBoardReset, this));
982  
983 this.initialize.apply(this, arguments);
984 return this;
985 };
986  
987 DrawingBoard.Control.prototype = {
988  
989 name: '',
990  
991 defaults: {},
992  
993 initialize: function() {
994  
995 },
996  
997 addToBoard: function() {
998 this.board.addControl(this);
999 },
1000  
1001 onBoardReset: function(opts) {
1002  
1003 }
1004  
1005 };
1006  
1007 //extend directly taken from backbone.js
1008 DrawingBoard.Control.extend = function(protoProps, staticProps) {
1009 var parent = this;
1010 var child;
1011 if (protoProps && protoProps.hasOwnProperty('constructor')) {
1012 child = protoProps.constructor;
1013 } else {
1014 child = function(){ return parent.apply(this, arguments); };
1015 }
1016 $.extend(child, parent, staticProps);
1017 var Surrogate = function(){ this.constructor = child; };
1018 Surrogate.prototype = parent.prototype;
1019 child.prototype = new Surrogate();
1020 if (protoProps) $.extend(child.prototype, protoProps);
1021 child.__super__ = parent.prototype;
1022 return child;
1023 };
1024 DrawingBoard.Control.Color = DrawingBoard.Control.extend({
1025 name: 'colors',
1026  
1027 initialize: function() {
1028 this.initTemplate();
1029  
1030 var that = this;
1031 this.$el.on('click', '.drawing-board-control-colors-picker', function(e) {
1032 var color = $(this).attr('data-color');
1033 that.board.setColor(color);
1034 that.$el.find('.drawing-board-control-colors-current')
1035 .css('background-color', color)
1036 .attr('data-color', color);
1037  
1038 that.board.ev.trigger('color:changed', color);
1039 that.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
1040  
1041 e.preventDefault();
1042 });
1043  
1044 this.$el.on('click', '.drawing-board-control-colors-current', function(e) {
1045 that.$el.find('.drawing-board-control-colors-rainbows').toggleClass('drawing-board-utils-hidden');
1046 e.preventDefault();
1047 });
1048  
1049 $('body').on('click', function(e) {
1050 var $target = $(e.target);
1051 var $relatedButton = $target.hasClass('drawing-board-control-colors-current') ? $target : $target.closest('.drawing-board-control-colors-current');
1052 var $myButton = that.$el.find('.drawing-board-control-colors-current');
1053 var $popup = that.$el.find('.drawing-board-control-colors-rainbows');
1054 if ( (!$relatedButton.length || $relatedButton.get(0) !== $myButton.get(0)) && !$popup.hasClass('drawing-board-utils-hidden') )
1055 $popup.addClass('drawing-board-utils-hidden');
1056 });
1057 },
1058  
1059 initTemplate: function() {
1060 var tpl = '<div class="drawing-board-control-inner">' +
1061 '<div class="drawing-board-control-colors-current" style="background-color: {{color}}" data-color="{{color}}"></div>' +
1062 '<div class="drawing-board-control-colors-rainbows">{{rainbows}}</div>' +
1063 '</div>';
1064 var oneColorTpl = '<div class="drawing-board-control-colors-picker" data-color="{{color}}" style="background-color: {{color}}"></div>';
1065 var rainbows = '';
1066 $.each([0.75, 0.5, 0.25], $.proxy(function(key, val) {
1067 var i = 0;
1068 var additionalColor = null;
1069 rainbows += '<div class="drawing-board-control-colors-rainbow">';
1070 if (val == 0.25) additionalColor = this._rgba(0, 0, 0, 1);
1071 if (val == 0.5) additionalColor = this._rgba(150, 150, 150, 1);
1072 if (val == 0.75) additionalColor = this._rgba(255, 255, 255, 1);
1073 rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: additionalColor.toString() });
1074 while (i <= 330) {
1075 rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: this._hsl2Rgba(this._hsl(i-60, 1, val)).toString() });
1076 i+=30;
1077 }
1078 rainbows += '</div>';
1079 }, this));
1080  
1081 this.$el.append( $( DrawingBoard.Utils.tpl(tpl, {color: this.board.color, rainbows: rainbows }) ) );
1082 this.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
1083 },
1084  
1085 onBoardReset: function(opts) {
1086 this.board.setColor(this.$el.find('.drawing-board-control-colors-current').attr('data-color'));
1087 },
1088  
1089 _rgba: function(r, g, b, a) {
1090 return { r: r, g: g, b: b, a: a, toString: function() { return "rgba(" + r +", " + g + ", " + b + ", " + a + ")"; } };
1091 },
1092  
1093 _hsl: function(h, s, l) {
1094 return { h: h, s: s, l: l, toString: function() { return "hsl(" + h +", " + s*100 + "%, " + l*100 + "%)"; } };
1095 },
1096  
1097 _hex2Rgba: function(hex) {
1098 var num = parseInt(hex.substring(1), 16);
1099 return this._rgba(num >> 16, num >> 8 & 255, num & 255, 1);
1100 },
1101  
1102 //conversion function (modified a bit) taken from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
1103 _hsl2Rgba: function(hsl) {
1104 var h = hsl.h/360, s = hsl.s, l = hsl.l, r, g, b;
1105 function hue2rgb(p, q, t) {
1106 if(t < 0) t += 1;
1107 < 0) t += 1; if(t > 1) t -= 1;
1108 < 0) t += 1; if(t < 1/6) return p + (q - p) * 6 * t;
1109 < 0) t += 1;< 1/ if(t < 1/2) return q;
1110 < 0) t += 1;< 1/ if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
1111 < 0) t += 1;< 1/< 2/ return p;
1112 < 0) t += 1;< 1/< 2/ }
1113 < 0) t += 1;< 1/< 2/ if (s === 0) {
1114 < 0) t += 1;< 1/< 2/ r = g = b = l; // achromatic
1115 < 0) t += 1;< 1/< 2/ } else {
1116 < 0) t += 1;< 1/< 2/ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1117 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q;
1118 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; r = Math.floor( (hue2rgb(p, q, h + 1/3)) * 255);
1119 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; g = Math.floor( (hue2rgb(p, q, h)) * 255);
1120 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; b = Math.floor( (hue2rgb(p, q, h - 1/3)) * 255);
1121 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; }
1122 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; return this._rgba(r, g, b, 1);
1123 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; }
1124 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;});
1125 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;DrawingBoard.Control.DrawingMode = DrawingBoard.Control.extend({
1126  
1127 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; name: 'drawingmode',
1128  
1129 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; defaults: {
1130 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; pencil: true,
1131 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; eraser: true,
1132 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; filler: true
1133 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; },
1134  
1135 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; initialize: function() {
1136  
1137 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; this.prevMode = this.board.getMode();
1138  
1139 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; $.each(["pencil", "eraser", "filler"], $.proxy(function(k, value) {
1140 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; if (this.opts[value]) {
1141 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s; this.$el.append('
1142 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1143 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1144  
1145 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1146 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1147 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1148 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1149 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1150 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1151 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1152 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1153  
1154 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1155 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1156 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1157  
1158 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1159 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1160  
1161 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1162 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1163 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1164 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1165 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1166 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1167  
1168 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1169  
1170 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1171  
1172 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1173  
1174 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1175 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1176 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1177 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1178 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1179  
1180 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1181 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1182 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1183 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1184 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1185 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1186  
1187 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1188 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1189 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1190 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1191 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1192 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1193 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1194  
1195 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1196 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1197  
1198 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1199 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1200 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1201 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1202 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1203 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1204 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1205  
1206 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1207 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1208  
1209 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1210 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1211 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1212 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1213 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1214 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1215 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1216  
1217 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1218 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1219 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1220 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1221 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1222 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1223 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1224  
1225 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1226 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1227 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1228 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1229 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1230 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1231 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1232 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1233 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1234  
1235 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1236  
1237 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1238 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1239 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1240 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1241 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1242 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1243  
1244 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1245  
1246 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1247 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1248 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1249 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1250 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1251  
1252 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1253  
1254 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1255 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1256 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1257  
1258 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1259  
1260 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1261 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1262 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1263 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1264  
1265 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1266  
1267 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1268 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1269 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1270  
1271 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1272 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1273 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1274 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1275  
1276 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1277 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1278 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1279  
1280 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1281  
1282 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1283 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1284 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1285 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1286  
1287 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1288 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1289 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1290 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1291 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1292 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1293 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1294 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1295 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1296 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1297 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1298  
1299 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1300 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1301 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1302 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1303 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1304 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1305 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1306 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1307 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1308 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1309 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1310 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1311 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1312  
1313 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1314 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1315 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1316  
1317 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1318 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1319 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1320  
1321 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1322 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1323 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1324 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1325 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1326 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1327 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1328  
1329 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1330  
1331 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1332 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1333 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1334 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1335 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1336 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1337 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1338 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1339 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1340  
1341 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1342 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1343 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1344 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1345 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1346 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1347 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1348 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1349 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1350 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1351 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1352 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1353 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1354 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1355 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1356 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1357 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1358 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1359 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1360 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1361 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1362 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1363  
1364 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1365  
1366 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1367 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1368 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1369 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1370 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1371 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1372 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;
1373  
1374 < 0) t += 1;< 1/< 2/< 0.5 ? l * (1 + s) : l + s - l * s;