PageRenderTime 75ms CodeModel.GetById 3ms app.highlight 36ms RepoModel.GetById 28ms app.codeStats 1ms

/wwwroot/widgets/whiteboard/whiteboard.js

http://github.com/AF83/ucengine
JavaScript | 553 lines | 328 code | 49 blank | 176 comment | 47 complexity | f3b7f0e4efbc5358561841ef93e9374c MD5 | raw file
  1/**
  2 * HTML5 Canvas Whiteboard
  3 * 
  4 * Authors:
  5 * Antti Hukkanen
  6 * Kristoffer Snabb
  7 * 
  8 * Aalto University School of Science and Technology
  9 * Course: T-111.2350 Multimediatekniikka / Multimedia Technology
 10 * 
 11 * Under MIT Licence
 12 * 
 13 */
 14
 15(function() {
 16	
 17/**
 18 * =============
 19 *     MODEL
 20 * =============
 21 */
 22
 23/* === BEGIN Event objects === */
 24
 25/* Begin path event */
 26function BeginPath(x, y) {
 27    this.coordinates = [x, y];
 28    this.type="beginpath";
 29	this.time = new Date().getTime();
 30}
 31/* Begin shape event */
 32function BeginShape(x, y, canvas) {
 33	this.type = "beginshape";
 34	this.canvas = canvas;
 35	this.coordinates = [x, y];
 36	this.time = new Date().getTime();
 37}
 38/* End path event */
 39function ClosePath() {
 40    this.type = "closepath";
 41	this.time = new Date().getTime();
 42}
 43/* Point draw event */
 44function DrawPathToPoint(x, y) {
 45    this.type = "drawpathtopoint";
 46    this.coordinates = [x, y];
 47    this.time = new Date().getTime();
 48}
 49/*Erase event */
 50function Erase(x, y) {
 51    this.type = "erase";
 52    this.coordinates = [x, y];
 53    this.height = 5;
 54    this.width = 5;
 55    this.time = new Date().getTime();
 56}
 57/* Rectangle event */
 58function Rectangle(sx, sy, ex, ey, canvas) {
 59	this.type = "rectangle";
 60	this.coordinates = [sx, sy, ex, ey];
 61	this.canvas = canvas;
 62	this.time = new Date().getTime();
 63}
 64function Oval(x, y, w, h, canvas) {
 65	this.type = "oval";
 66	this.coordinates = [x, y, w, h];
 67	this.canvas = canvas;
 68	this.time = new Date().getTime();
 69}
 70/* Storke style event */
 71function StrokeStyle(color) {
 72    this.type = "strokestyle";
 73    this.color = color;
 74    this.time = new Date().getTime();
 75}
 76/* Zoom event */
 77function Zoom(factor) {
 78    this.type = "zoom";
 79    this.factor = factor;
 80    this.time = new Date().getTime();
 81}
 82/* Restore event */
 83function Restore(canvas) {
 84	this.type = "restore";
 85	if (canvas !== undefined) {
 86		this.canvas = canvas;
 87	}
 88	this.time = new Date().getTime();
 89}
 90/* Rotate event
 91   angle in degrees
 92*/
 93function Rotate(angle) {
 94    this.type = "rotate";
 95    this.angle = angle;
 96    this.time = new Date().getTime();
 97}
 98/* === END Event objects === */
 99
100
101	
102/**
103 * ====================
104 *    STATIC CONTROL
105 * ====================   
106 */
107window.Whiteboard = {
108
109    context: null,
110    canvas: null,
111    type: '',
112    coordinates: [0,0],
113    events: [],
114    animationind: 0,
115
116    drawColor: '#000000',
117
118    /**
119     * Initializes the script by setting the default
120     * values for parameters of the class.
121     * 
122     * @param canvasid The id of the canvas element used
123     */
124    init: function(canvasid) {
125	    // set the canvas width and height
126	    // the offsetWidth and Height is default width and height
127	    this.canvas = document.getElementById(canvasid);
128	    this.canvas.width = this.canvas.offsetWidth;
129	    this.canvas.height = this.canvas.offsetHeight;
130	
131	    this.context = this.canvas.getContext('2d');
132	
133	    //initial values for the drawing context
134	    this.context.lineWidth = 5;
135	    this.context.lineCap = "round";
136	    var zoomFactor = 1.0;
137	
138	    // Initialize the selected color
139	    var col = this.drawColor;
140	    this.drawColor = null;
141	    this.setStrokeStyle(col);
142    },
143
144    /**
145     * Executes the event that matches the given event
146     * object
147     * 
148     * @param wbevent The event object to be executed.
149     * @param firstexecute tells the function if the event is new and
150     *          should be saved to this.events
151     * This object should be one of the model's event objects.
152     */
153    execute: function(wbevent, firstexecute) {
154        var type = wbevent.type;
155        var wid;
156        var hei;
157        var tmp;
158        if(firstexecute || firstexecute === undefined) {
159            wbevent.time = new Date().getTime();
160            this.events.push(wbevent);
161        }
162
163        if(type === "beginpath") {
164            this.context.beginPath();
165            this.context.moveTo(wbevent.coordinates[0],
166                           wbevent.coordinates[1]);
167            this.context.stroke();
168        } else if(type === "beginshape") {
169	        this.context.save();
170	        this.context.beginPath();
171        } else if (type === "drawpathtopoint") {  
172            this.context.lineTo(wbevent.coordinates[0],
173                           wbevent.coordinates[1]);
174            this.context.stroke();
175        } else if (type === "closepath") {
176            this.context.closePath();
177        } else if(type === "strokestyle") {
178            this.context.strokeStyle = wbevent.color;
179        } else if(type === "zoom") {
180            var newWidth = this.canvas.offsetWidth * wbevent.factor;
181            var newHeight = this.canvas.offsetHeight * wbevent.factor;
182            this.canvas.style.width = newWidth + "px";
183            this.canvas.style.height = newHeight + "px";
184        } else if (type === "restore") {
185            wid = this.canvas.width;
186            hei = this.canvas.height;
187	        this.context.clearRect(0, 0, wid, hei);
188	        if (wbevent.canvas !== undefined) {
189		        this.context.drawImage(wbevent.canvas, 0, 0);
190	        }
191        } else if(type === "rotate") {
192            var radian = wbevent.angle * Math.PI / 180;
193            wid = this.canvas.width;
194            hei = this.canvas.height;
195            
196            tmp = document.createElement("canvas");
197            var tmpcnv = tmp.getContext('2d');
198            tmp.width = wid;
199            tmp.height = hei;
200            tmpcnv.drawImage(this.canvas, 0, 0);
201            
202            // TODO: Fix: the image blurs out after multiple rotations 
203            this.context.save();
204            this.context.clearRect(0, 0, wid, hei);
205            this.context.translate(wid/2,hei/2);
206            this.context.rotate(radian);
207            this.context.translate(-wid/2,-hei/2);
208            this.context.drawImage(tmp, 0, 0);
209            this.context.restore();
210            
211            tmp = tmpcnv = undefined;
212        } else if (type === "erase") {
213            this.context.clearRect(wbevent.coordinates[0],
214                              wbevent.coordinates[1],
215                              wbevent.width,
216                              wbevent.height);
217        } else if (type === "rectangle") {
218	        var sx = wbevent.coordinates[0];
219	        var sy = wbevent.coordinates[1];
220	        var ex = wbevent.coordinates[2];
221	        var ey = wbevent.coordinates[3];
222	        tmp = 0;
223	        if (ex < sx) {
224		        tmp = sx;
225		        sx = ex;
226		        ex = tmp;
227	        }
228	        if (ey < sy) {
229		        tmp = sy;
230		        sy = ey;
231		        ey = tmp;
232	        }
233	
234	        if (wbevent.canvas !== undefined) {
235                wid = this.canvas.width;
236                hei = this.canvas.height;
237		        this.context.clearRect(0, 0, wid, hei);
238		        this.context.drawImage(wbevent.canvas, 0, 0);
239	        }
240	        this.context.beginPath();
241	        this.context.rect(sx, sy, ex-sx, ey-sy);
242	        this.context.closePath();
243	        this.context.stroke();
244        } else if (type === "oval") {
245	        var x = wbevent.coordinates[0];
246	        var y = wbevent.coordinates[1];
247	        var w = wbevent.coordinates[2];
248	        var h = wbevent.coordinates[3];
249	
250	        var kappa = 0.5522848;
251	        var ox = (w / 2) * kappa;
252	        var oy = (h / 2) * kappa;
253	        var xe = x + w;
254	        var ye = y + h;
255	        var xm = x + w / 2;
256	        var ym = y + h / 2;
257	
258	        if (wbevent.canvas !== undefined) {
259                wid = this.canvas.width;
260                hei = this.canvas.height;
261		        this.context.clearRect(0, 0, wid, hei);
262		        this.context.drawImage(wbevent.canvas, 0, 0);
263	        }
264	
265	        this.context.beginPath();
266	        this.context.moveTo(x, ym);
267	        this.context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
268	        this.context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
269	        this.context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
270	        this.context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
271	        this.context.closePath();
272	        this.context.stroke();
273        }
274    },
275
276    /**
277     * Resolves the relative width and height of the canvas
278     * element. Relative parameters can vary depending on the
279     * zoom. Both are equal to 1 if no zoom is encountered.
280     * 
281     * @return An array containing the relative width as first
282     * element and relative height as second.
283     */
284    getRelative: function() {
285	    return {width: this.canvas.width/this.canvas.offsetWidth,
286			    height: this.canvas.height/this.canvas.offsetHeight};
287    },
288
289    /**
290     * Opens a new window and writes the canvas image data
291     * to an element of type "img" in the new windows html body.
292     * The image is written in the specified image form.
293     * 
294     * @param type The output image type.
295     * Alternatives: png, jpg/jpeg, bmp
296     */
297    saveAs: function(type) {
298	    if (type === undefined) {
299		    type = "png";
300	    }
301	    type = type.toLowerCase();
302	
303	    var img = null;
304	    if (type == 'jpg' || type == 'jpeg') {
305		    img = Canvas2Image.saveAsJPEG(Whiteboard.canvas, true);
306	    } else if (type == 'bmp') {
307		    img = Canvas2Image.saveAsBMP(Whiteboard.canvas, true);
308	    } else {
309		    img = Canvas2Image.saveAsPNG(Whiteboard.canvas, true);
310	    }
311	    var options = 'width=' + Whiteboard.canvas.width + ',' +
312		    'height=' + Whiteboard.canvas.height;
313	    var win = window.open('','Save image',options);
314	    win.innerHTML = "";
315	    win.document.body.appendChild(img);
316    },
317
318
319    /* === BEGIN ACTIONS === */
320
321    /**
322     * Starts the animation action in the canvas. This clears
323     * the whole canvas and starts to execute actions from
324     * the action stack by calling Whiteboard.animatenext().
325     */
326    animate: function() {
327	    Whiteboard.animationind = 0;
328	    Whiteboard.context.clearRect(0,0,Whiteboard.canvas.width,Whiteboard.canvas.height);
329	    Whiteboard.animatenext();
330    },
331
332    /**
333     * This function animates the next event in the event 
334     * stack and waits for the amount of time between the 
335     * current and next event before calling itself again.
336     */
337    animatenext: function() {
338        if(Whiteboard.animationind === 0) {
339            Whiteboard.execute(Whiteboard.events[0], false);
340            Whiteboard.animationind++;   
341        }
342        
343        Whiteboard.execute(Whiteboard.events[Whiteboard.animationind], false);
344        Whiteboard.animationind++;
345        
346        if (Whiteboard.animationind < Whiteboard.events.length - 1) {
347            var now = new Date().getTime();
348	        var dtime = Whiteboard.events[Whiteboard.animationind+1].time - Whiteboard.events[Whiteboard.animationind].time;
349            setTimeout(Whiteboard.animatenext, dtime);
350        }
351    },
352
353    /**
354     * Begins a drawing path.
355     * 
356     * @param x Coordinate x of the path starting point
357     * @param y Coordinate y of the path starting point
358     */
359    beginPencilDraw: function(x, y) {
360        var e = new BeginPath(x, y);
361        Whiteboard.execute(e);
362    },
363
364    /**
365     * Draws a path from the path starting point to the
366     * point indicated by the given parameters.
367     * 
368     * @param x Coordinate x of the path ending point
369     * @param y Coordinate y of the path ending point
370     */
371    pencilDraw: function(x, y) {
372        var e = new DrawPathToPoint(x, y);
373        Whiteboard.execute(e);
374    },
375
376    /**
377     * Begins erasing path.
378     * 
379     * @param x Coordinate x of the path starting point
380     * @param y Coordinate y of the path starting point
381     */
382    beginErasing: function(x, y) {
383        var e = new BeginPath(x, y);
384        Whiteboard.execute(e);
385    },
386
387    /**
388     * Erases the point indicated by the given coordinates.
389     * Actually this doesn't take the path starting point
390     * into account but erases a rectangle at the given
391     * coordinates with width and height specified in the
392     * Erase object.
393     * 
394     * @param x Coordinate x of the path ending point
395     * @param y Coordinate y of the path ending point
396     */
397    erasePoint: function(x, y) {
398        var e = new Erase(x, y);
399        Whiteboard.execute(e);
400    },
401
402    /**
403     * Begins shape drawing. The shape starting point
404     * should be the point where user click the canvas the
405     * first time after choosing the shape tool.
406     * 
407     * @param x Coordinate x of the shape starting point
408     * @param y Coordinate y of the shape starting point
409     */
410    beginShape: function(x, y) {
411        var tmp = document.createElement("canvas");
412        var tmpcnv = tmp.getContext('2d');
413        tmp.width = Whiteboard.canvas.width;
414        tmp.height = Whiteboard.canvas.height;
415        tmpcnv.drawImage(Whiteboard.canvas, 0, 0);
416	    var e = new BeginShape(x, y, tmp);
417	    Whiteboard.execute(e);
418    },
419
420    /**
421     * Draws a rectangle that has opposite corner to the
422     * shape starting point at the given point.
423     * 
424     * @param x Coordinate x of the shape ending point
425     * @param y Coordinate y of the shape ending point
426     */
427    drawRectangle: function(x, y) {
428	    var i = Whiteboard.events.length - 1;
429	    while (i >= 0) {
430		    var e = Whiteboard.events[i];
431		    if (e.type === "beginshape") {
432			    var ev = new Rectangle(e.coordinates[0], e.coordinates[1], x, y, e.canvas);
433			    Whiteboard.execute(ev);
434			    e = ev = undefined;
435			    break;
436		    }
437		    i--;
438	    }
439    },
440
441    /**
442     * Draws an oval that has opposite corner to the
443     * shape starting point at the given point. The oval
444     * drawing can be visualized by the same way as drawing
445     * a rectangle but the corners are round.
446     * 
447     * @param x Coordinate x of the shape ending point
448     * @param y Coordinate y of the shape ending point
449     */
450    drawOval: function(x, y) {
451	    var i = Whiteboard.events.length - 1;
452	    while (i >= 0) {
453		    var e = Whiteboard.events[i];
454		    if (e.type === "beginshape") {
455			    var sx = e.coordinates[0];
456			    var sy = e.coordinates[1];
457			    var wid = x-sx;
458			    var hei = y-sy;
459			
460			    var ev = new Oval(sx, sy, wid, hei, e.canvas);
461			    Whiteboard.execute(ev);
462			    e = ev = undefined;
463			    break;
464		    }
465		    i--;
466	    }
467    },
468
469    /**
470     * Sets stroke style for the canvas. Stroke
471     * style defines the color with which every
472     * stroke should be drawn on the canvas.
473     * 
474     * @param color The wanted stroke color
475     */
476    setStrokeStyle: function(color) {
477	    if (color != Whiteboard.drawColor) {
478		    var e = new StrokeStyle(color);
479		    Whiteboard.execute(e);
480	    }
481    },
482
483    /**
484     * Zooms in the canvas 50% of the current canvas size
485     * resulting a 150% image size.
486     */
487    zoomin: function() {
488        var e = new Zoom(1.5);
489        Whiteboard.execute(e);
490    },
491
492    /**
493     * Zooms out the canvas 50% of the current canvas size
494     * resulting a 50% image size.
495     */
496    zoomout: function() {
497        var e = new Zoom(0.5);
498        Whiteboard.execute(e);
499    },
500
501    /**
502     * Zooms the canvas amount specified by the factor
503     * parameter.
504     * 
505     * @param factor The zoom factor. > 1 if zooming in
506     * and < 1 if zooming out.
507     */
508    zoom: function(factor) {
509        var e = new Zoom(factor);
510        Whiteboard.execute(e);
511    },
512
513    /**
514     * Rotates the canvas by the degrees defined by
515     * the degree parameter.
516     * 
517     * @param degree The degree of the rotation event.
518     */
519    rotate: function(degree) {
520        var e = new Rotate(degree);
521        Whiteboard.execute(e);
522    },
523
524    /**
525     * This function redraws the entire canvas 
526     * according to the events in events.
527    */
528    redraw: function() {
529        //this.init();
530	    Whiteboard.context.clearRect(0,0,Whiteboard.canvas.width,Whiteboard.canvas.height);
531        var redrawEvents = this.events;
532        this.events = [];
533        
534        for(var i=0;i < redrawEvents.length; i++) {
535            this.execute(redrawEvents[i]);
536        }
537    },
538
539    /**
540     * This removes the last event from this events 
541     * and redraws (it can be made more 
542     * effective then to redraw but time is limited)
543    */
544    undo: function() {
545        reverseEvent = this.events.pop();
546        console.log(reverseEvent.type);
547        this.redraw();
548    }
549
550    /* === END ACTIONS === */
551
552    };
553})();