5715b010512251c27cbabb6e730ac0899adc6d2d
[roojs1] / Roo / bootstrap / BezierSignature.js
1 /**
2 *    This script refer to:
3 *    Title: Signature Pad
4 *    Author: szimek
5 *    Availability: https://github.com/szimek/signature_pad
6 **/
7
8 /**
9  * @class Roo.bootstrap.BezierSignature
10  * @extends Roo.bootstrap.Component
11  * Bootstrap BezierSignature class
12  * 
13  * @constructor
14  * Create a new BezierSignature
15  * @param {Object} config The config object
16  */
17
18 Roo.bootstrap.BezierSignature = function(config){
19     Roo.bootstrap.BezierSignature.superclass.constructor.call(this, config);
20     this.addEvents({
21         "resize" : true
22     });
23 };
24
25 Roo.extend(Roo.bootstrap.BezierSignature, Roo.bootstrap.Component,
26 {
27      
28     curve_data: [],
29     
30     is_empty: true,
31     
32     mouse_btn_down: true,
33     
34     /**
35      * @cfg {int} canvas height
36      */
37     canvas_height: '200px',
38     
39     /**
40      * @cfg {float|function} Radius of a single dot.
41      */ 
42     dot_size: false,
43     
44     /**
45      * @cfg {float} Minimum width of a line. Defaults to 0.5.
46      */
47     min_width: 0.5,
48     
49     /**
50      * @cfg {float} Maximum width of a line. Defaults to 2.5.
51      */
52     max_width: 2.5,
53     
54     /**
55      * @cfg {integer} Draw the next point at most once per every x milliseconds. Set it to 0 to turn off throttling. Defaults to 16.
56      */
57     throttle: 16,
58     
59     /**
60      * @cfg {integer} Add the next point only if the previous one is farther than x pixels. Defaults to 5.
61      */
62     min_distance: 5,
63     
64     /**
65      * @cfg {string} Color used to clear the background. Can be any color format accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque white) if you'd like to save signatures as JPEG images.
66      */
67     bg_color: 'rgba(0, 0, 0, 0)',
68     
69     /**
70      * @cfg {string} Color used to draw the lines. Can be any color format accepted by context.fillStyle. Defaults to "black".
71      */
72     dot_color: 'black',
73     
74     /**
75      * @cfg {float} Weight used to modify new velocity based on the previous velocity. Defaults to 0.7.
76      */ 
77     velocity_filter_weight: 0.7,
78     
79     /**
80      * @cfg {function} Callback when stroke begin. 
81      */
82     onBegin: false,
83     
84     /**
85      * @cfg {function} Callback when stroke end.
86      */
87     onEnd: false,
88     
89     getAutoCreate : function()
90     {
91         var cls = 'roo-signature column';
92         
93         if(this.cls){
94             cls += ' ' + this.cls;
95         }
96         
97         var col_sizes = [
98             'lg',
99             'md',
100             'sm',
101             'xs'
102         ];
103         
104         for(var i = 0; i < col_sizes.length; i++) {
105             if(this[col_sizes[i]]) {
106                 cls += " col-"+col_sizes[i]+"-"+this[col_sizes[i]];
107             }
108         }
109         
110         var cfg = {
111             tag: 'div',
112             cls: cls,
113             cn: [
114                 {
115                     tag: 'div',
116                     cls: 'roo-signature-body',
117                     cn: [
118                         {
119                             tag: 'canvas',
120                             cls: 'roo-signature-body-canvas',
121                             height: this.canvas_height,
122                             width: this.canvas_width
123                         }
124                     ]
125                 },
126                 {
127                     tag: 'input',
128                     type: 'file',
129                     style: 'display: none'
130                 }
131             ]
132         };
133         
134         return cfg;
135     },
136     
137     initEvents: function() 
138     {
139         Roo.bootstrap.BezierSignature.superclass.initEvents.call(this);
140         
141         var canvas = this.canvasEl();
142         
143         // mouse && touch event swapping...
144         canvas.dom.style.touchAction = 'none';
145         canvas.dom.style.msTouchAction = 'none';
146         
147         this.mouse_btn_down = false;
148         canvas.on('mousedown', this._handleMouseDown, this);
149         canvas.on('mousemove', this._handleMouseMove, this);
150         Roo.select('html').first().on('mouseup', this._handleMouseUp, this);
151         
152         if (window.PointerEvent) {
153             canvas.on('pointerdown', this._handleMouseDown, this);
154             canvas.on('pointermove', this._handleMouseMove, this);
155             Roo.select('html').first().on('pointerup', this._handleMouseUp, this);
156         }
157         
158         if ('ontouchstart' in window) {
159             canvas.on('touchstart', this._handleTouchStart, this);
160             canvas.on('touchmove', this._handleTouchMove, this);
161             canvas.on('touchend', this._handleTouchEnd, this);
162         }
163         
164         Roo.EventManager.onWindowResize(this.resize, this, true);
165         
166         // file input event
167         this.fileEl().on('change', this.uploadImage, this);
168         
169         this.clear();
170         
171         this.resize();
172     },
173     
174     resize: function(){
175         
176         var canvas = this.canvasEl().dom;
177         var ctx = this.canvasElCtx();
178         var img_data = false;
179         
180         if(canvas.width > 0) {
181             var img_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
182         }
183         // setting canvas width will clean img data
184         canvas.width = 0;
185         
186         var style = window.getComputedStyle ? 
187             getComputedStyle(this.el.dom, null) : this.el.dom.currentStyle;
188             
189         var padding_left = parseInt(style.paddingLeft) || 0;
190         var padding_right = parseInt(style.paddingRight) || 0;
191         
192         canvas.width = this.el.dom.clientWidth - padding_left - padding_right;
193         
194         if(img_data) {
195             ctx.putImageData(img_data, 0, 0);
196         }
197     },
198     
199     _handleMouseDown: function(e)
200     {
201         if (e.browserEvent.which === 1) {
202             this.mouse_btn_down = true;
203             this.strokeBegin(e);
204         }
205     },
206     
207     _handleMouseMove: function (e)
208     {
209         if (this.mouse_btn_down) {
210             this.strokeMoveUpdate(e);
211         }
212     },
213     
214     _handleMouseUp: function (e)
215     {
216         if (e.browserEvent.which === 1 && this.mouse_btn_down) {
217             this.mouse_btn_down = false;
218             this.strokeEnd(e);
219         }
220     },
221     
222     _handleTouchStart: function (e) {
223         
224         e.preventDefault();
225         if (e.browserEvent.targetTouches.length === 1) {
226             // var touch = e.browserEvent.changedTouches[0];
227             // this.strokeBegin(touch);
228             
229              this.strokeBegin(e); // assume e catching the correct xy...
230         }
231     },
232     
233     _handleTouchMove: function (e) {
234         e.preventDefault();
235         // var touch = event.targetTouches[0];
236         // _this._strokeMoveUpdate(touch);
237         this.strokeMoveUpdate(e);
238     },
239     
240     _handleTouchEnd: function (e) {
241         var wasCanvasTouched = e.target === this.canvasEl().dom;
242         if (wasCanvasTouched) {
243             e.preventDefault();
244             // var touch = event.changedTouches[0];
245             // _this._strokeEnd(touch);
246             this.strokeEnd(e);
247         }
248     },
249     
250     reset: function () {
251         this._lastPoints = [];
252         this._lastVelocity = 0;
253         this._lastWidth = (this.min_width + this.max_width) / 2;
254         this.canvasElCtx().fillStyle = this.dot_color;
255     },
256     
257     strokeMoveUpdate: function(e)
258     {
259         this.strokeUpdate(e);
260         
261         if (this.throttle) {
262             this.throttle(this.strokeUpdate, this.throttle);
263         }
264         else {
265             this.strokeUpdate(e);
266         }
267     },
268     
269     strokeBegin: function(e)
270     {
271         var newPointGroup = {
272             color: this.dot_color,
273             points: []
274         };
275         
276         if (typeof this.onBegin === 'function') {
277             this.onBegin(e);
278         }
279         
280         this.curve_data.push(newPointGroup);
281         this.reset();
282         this.strokeUpdate(e);
283     },
284     
285     strokeUpdate: function(e)
286     {
287         var rect = this.canvasEl().dom.getBoundingClientRect();
288         var point = new this.Point(e.xy[0] - rect.left, e.xy[1] - rect.top, new Date().getTime());
289         var lastPointGroup = this.curve_data[this.curve_data.length - 1];
290         var lastPoints = lastPointGroup.points;
291         var lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
292         var isLastPointTooClose = lastPoint
293             ? point.distanceTo(lastPoint) <= this.min_distance
294             : false;
295         var color = lastPointGroup.color;
296         if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
297             var curve = this.addPoint(point);
298             if (!lastPoint) {
299                 this.drawDot({color: color, point: point});
300             }
301             else if (curve) {
302                 this.drawCurve({color: color, curve: curve});
303             }
304             lastPoints.push({
305                 time: point.time,
306                 x: point.x,
307                 y: point.y
308             });
309         }
310     },
311     
312     strokeEnd: function(e)
313     {
314         this.strokeUpdate(e);
315         if (typeof this.onEnd === 'function') {
316             this.onEnd(e);
317         }
318     },
319     
320     addPoint:  function (point) {
321         var _lastPoints = this._lastPoints;
322         _lastPoints.push(point);
323         if (_lastPoints.length > 2) {
324             if (_lastPoints.length === 3) {
325                 _lastPoints.unshift(_lastPoints[0]);
326             }
327             var widths = this.calculateCurveWidths(_lastPoints[1], _lastPoints[2]);
328             var curve = this.Bezier.fromPoints(_lastPoints, widths, this);
329             _lastPoints.shift();
330             return curve;
331         }
332         return null;
333     },
334     
335     calculateCurveWidths: function (startPoint, endPoint) {
336         var velocity = this.velocity_filter_weight * endPoint.velocityFrom(startPoint) +
337             (1 - this.velocity_filter_weight) * this._lastVelocity;
338
339         var newWidth = Math.max(this.max_width / (velocity + 1), this.min_width);
340         var widths = {
341             end: newWidth,
342             start: this._lastWidth
343         };
344         
345         this._lastVelocity = velocity;
346         this._lastWidth = newWidth;
347         return widths;
348     },
349     
350     drawDot: function (_a) {
351         var color = _a.color, point = _a.point;
352         var ctx = this.canvasElCtx();
353         var width = typeof this.dot_size === 'function' ? this.dot_size() : this.dot_size;
354         ctx.beginPath();
355         this.drawCurveSegment(point.x, point.y, width);
356         ctx.closePath();
357         ctx.fillStyle = color;
358         ctx.fill();
359     },
360     
361     drawCurve: function (_a) {
362         var color = _a.color, curve = _a.curve;
363         var ctx = this.canvasElCtx();
364         var widthDelta = curve.endWidth - curve.startWidth;
365         var drawSteps = Math.floor(curve.length()) * 2;
366         ctx.beginPath();
367         ctx.fillStyle = color;
368         for (var i = 0; i < drawSteps; i += 1) {
369         var t = i / drawSteps;
370         var tt = t * t;
371         var ttt = tt * t;
372         var u = 1 - t;
373         var uu = u * u;
374         var uuu = uu * u;
375         var x = uuu * curve.startPoint.x;
376         x += 3 * uu * t * curve.control1.x;
377         x += 3 * u * tt * curve.control2.x;
378         x += ttt * curve.endPoint.x;
379         var y = uuu * curve.startPoint.y;
380         y += 3 * uu * t * curve.control1.y;
381         y += 3 * u * tt * curve.control2.y;
382         y += ttt * curve.endPoint.y;
383         var width = curve.startWidth + ttt * widthDelta;
384         this.drawCurveSegment(x, y, width);
385         }
386         ctx.closePath();
387         ctx.fill();
388     },
389     
390     drawCurveSegment: function (x, y, width) {
391         var ctx = this.canvasElCtx();
392         ctx.moveTo(x, y);
393         ctx.arc(x, y, width, 0, 2 * Math.PI, false);
394         this.is_empty = false;
395     },
396     
397     clear: function()
398     {
399         var ctx = this.canvasElCtx();
400         var canvas = this.canvasEl().dom;
401         ctx.fillStyle = this.bg_color;
402         ctx.clearRect(0, 0, canvas.width, canvas.height);
403         ctx.fillRect(0, 0, canvas.width, canvas.height);
404         this.curve_data = [];
405         this.reset();
406         this.is_empty = true;
407     },
408     
409     fileEl: function()
410     {
411         return  this.el.select('input',true).first();
412     },
413     
414     canvasEl: function()
415     {
416         return this.el.select('canvas',true).first();
417     },
418     
419     canvasElCtx: function()
420     {
421         return this.el.select('canvas',true).first().dom.getContext('2d');
422     },
423     
424     getImage: function(type)
425     {
426         if(this.is_empty) {
427             return false;
428         }
429         
430         // encryption ?
431         return this.canvasEl().dom.toDataURL('image/'+type, 1);
432     },
433     
434     drawFromImage: function(img_src)
435     {
436         var img = new Image();
437         
438         img.onload = function(){
439             this.canvasElCtx().drawImage(img, 0, 0);
440         }.bind(this);
441         
442         img.src = img_src;
443         
444         this.is_empty = false;
445     },
446     
447     selectImage: function()
448     {
449         this.fileEl().dom.click();
450     },
451     
452     uploadImage: function(e)
453     {
454         var reader = new FileReader();
455         
456         reader.onload = function(e){
457             var img = new Image();
458             img.onload = function(){
459                 this.reset();
460                 this.canvasElCtx().drawImage(img, 0, 0);
461             }.bind(this);
462             img.src = e.target.result;
463         }.bind(this);
464         
465         reader.readAsDataURL(e.target.files[0]);
466     },
467     
468     // Bezier Point Constructor
469     Point: (function () {
470         function Point(x, y, time) {
471             this.x = x;
472             this.y = y;
473             this.time = time || Date.now();
474         }
475         Point.prototype.distanceTo = function (start) {
476             return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
477         };
478         Point.prototype.equals = function (other) {
479             return this.x === other.x && this.y === other.y && this.time === other.time;
480         };
481         Point.prototype.velocityFrom = function (start) {
482             return this.time !== start.time
483             ? this.distanceTo(start) / (this.time - start.time)
484             : 0;
485         };
486         return Point;
487     }()),
488     
489     
490     // Bezier Constructor
491     Bezier: (function () {
492         function Bezier(startPoint, control2, control1, endPoint, startWidth, endWidth) {
493             this.startPoint = startPoint;
494             this.control2 = control2;
495             this.control1 = control1;
496             this.endPoint = endPoint;
497             this.startWidth = startWidth;
498             this.endWidth = endWidth;
499         }
500         Bezier.fromPoints = function (points, widths, scope) {
501             var c2 = this.calculateControlPoints(points[0], points[1], points[2], scope).c2;
502             var c3 = this.calculateControlPoints(points[1], points[2], points[3], scope).c1;
503             return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
504         };
505         Bezier.calculateControlPoints = function (s1, s2, s3, scope) {
506             var dx1 = s1.x - s2.x;
507             var dy1 = s1.y - s2.y;
508             var dx2 = s2.x - s3.x;
509             var dy2 = s2.y - s3.y;
510             var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
511             var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
512             var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
513             var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
514             var dxm = m1.x - m2.x;
515             var dym = m1.y - m2.y;
516             var k = l2 / (l1 + l2);
517             var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
518             var tx = s2.x - cm.x;
519             var ty = s2.y - cm.y;
520             return {
521                 c1: new scope.Point(m1.x + tx, m1.y + ty),
522                 c2: new scope.Point(m2.x + tx, m2.y + ty)
523             };
524         };
525         Bezier.prototype.length = function () {
526             var steps = 10;
527             var length = 0;
528             var px;
529             var py;
530             for (var i = 0; i <= steps; i += 1) {
531                 var t = i / steps;
532                 var cx = this.point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
533                 var cy = this.point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
534                 if (i > 0) {
535                     var xdiff = cx - px;
536                     var ydiff = cy - py;
537                     length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
538                 }
539                 px = cx;
540                 py = cy;
541             }
542             return length;
543         };
544         Bezier.prototype.point = function (t, start, c1, c2, end) {
545             return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
546             + (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
547             + (3.0 * c2 * (1.0 - t) * t * t)
548             + (end * t * t * t);
549         };
550         return Bezier;
551     }()),
552     
553     throttle: function(fn, wait) {
554       if (wait === void 0) { wait = 250; }
555       var previous = 0;
556       var timeout = null;
557       var result;
558       var storedContext;
559       var storedArgs;
560       var later = function () {
561           previous = Date.now();
562           timeout = null;
563           result = fn.apply(storedContext, storedArgs);
564           if (!timeout) {
565               storedContext = null;
566               storedArgs = [];
567           }
568       };
569       return function wrapper() {
570           var args = [];
571           for (var _i = 0; _i < arguments.length; _i++) {
572               args[_i] = arguments[_i];
573           }
574           var now = Date.now();
575           var remaining = wait - (now - previous);
576           storedContext = this;
577           storedArgs = args;
578           if (remaining <= 0 || remaining > wait) {
579               if (timeout) {
580                   clearTimeout(timeout);
581                   timeout = null;
582               }
583               previous = now;
584               result = fn.apply(storedContext, storedArgs);
585               if (!timeout) {
586                   storedContext = null;
587                   storedArgs = [];
588               }
589           }
590           else if (!timeout) {
591               timeout = window.setTimeout(later, remaining);
592           }
593           return result;
594       };
595   }
596   
597 });
598
599  
600
601