PageRenderTime 33ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

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