You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pencil.js 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * WHITEBOPHIR
  3. *********************************************************
  4. * @licstart The following is the entire license notice for the
  5. * JavaScript code in this page.
  6. *
  7. * Copyright (C) 2013 Ophir LOJKINE
  8. *
  9. *
  10. * The JavaScript code in this page is free software: you can
  11. * redistribute it and/or modify it under the terms of the GNU
  12. * General Public License (GNU GPL) as published by the Free Software
  13. * Foundation, either version 3 of the License, or (at your option)
  14. * any later version. The code is distributed WITHOUT ANY WARRANTY;
  15. * without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
  17. *
  18. * As additional permission under GNU GPL version 3 section 7, you
  19. * may distribute non-source (e.g., minimized or compacted) forms of
  20. * that code without the copy of the GNU GPL normally required by
  21. * section 4, provided you include this license notice and a URL
  22. * through which recipients can access the Corresponding Source.
  23. *
  24. * @licend
  25. */
  26. (function () { //Code isolation
  27. //Indicates the id of the line the user is currently drawing or an empty string while the user is not drawing
  28. var curLineId = "",
  29. curPoint = { //The data of the message that will be sent for every new point
  30. 'type': 'child',
  31. 'parent': "",
  32. 'x': 0,
  33. 'y': 0
  34. },
  35. lastTime = performance.now(); //The time at which the last point was drawn
  36. function startLine(x, y, evt) {
  37. //Prevent the press from being interpreted by the browser
  38. evt.preventDefault();
  39. curLineId = Tools.generateUID("l"); //"l" for line
  40. Tools.drawAndSend({
  41. 'type': 'line',
  42. 'id': curLineId,
  43. 'color': Tools.getColor(),
  44. 'size': Tools.getSize()
  45. });
  46. //Update the current point
  47. curPoint.parent = curLineId;
  48. //Immediatly add a point to the line
  49. continueLine(x, y);
  50. }
  51. function continueLine(x, y, evt) {
  52. /*Wait 70ms before adding any point to the currently drawing line.
  53. This allows the animation to be smother*/
  54. if (curLineId !== "" &&
  55. performance.now() - lastTime > 70) {
  56. curPoint.x = x; curPoint.y = y;
  57. Tools.drawAndSend(curPoint);
  58. lastTime = performance.now();
  59. }
  60. if (evt) evt.preventDefault();
  61. }
  62. function stopLine(x, y) {
  63. //Add a last point to the line
  64. continueLine(x, y);
  65. curLineId = "";
  66. }
  67. var renderingLine = {};
  68. function draw(data) {
  69. switch (data.type) {
  70. case "line":
  71. renderingLine = createLine(data);
  72. break;
  73. case "child":
  74. var line = (renderingLine.id == data.parent) ? renderingLine : svg.getElementById(data.parent);
  75. if (!line) {
  76. console.error("Pencil: Hmmm... I received a point of a line that has not been created (%s).", data.parent);
  77. line = renderingLine = createLine({ "id": data.parent }); //create a new line in order not to loose the points
  78. }
  79. addPoint(line, data.x, data.y);
  80. break;
  81. case "endline":
  82. //TODO?
  83. break;
  84. default:
  85. console.error("Pencil: Draw instruction with unknown type. ", data);
  86. break;
  87. }
  88. }
  89. function dist(x1, y1, x2, y2) {
  90. //Returns the distance between (x1,y1) and (x2,y2)
  91. return Math.hypot(x2 - x1, y2 - y1);
  92. }
  93. var svg = Tools.svg;
  94. function addPoint(line, x, y) {
  95. var pts = line.getPathData(), //The points that are already in the line as a PathData
  96. nbr = pts.length; //The number of points already in the line
  97. switch (nbr) {
  98. case 0: //The first point in the line
  99. //If there is no point, we have to start the line with a moveTo statement
  100. npoint = { type: "M", values: [x, y] };
  101. break;
  102. case 1: //There is only one point.
  103. //Draw a curve that is segment between the old point and the new one
  104. npoint = {
  105. type: "C", values: [
  106. pts[0].values[0], pts[0].values[1],
  107. x, y,
  108. x, y,
  109. ]
  110. };
  111. break;
  112. default: //There are at least two points in the line
  113. //We add the new point, and smoothen the line
  114. var ANGULARITY = 3; //The lower this number, the smoother the line
  115. var prev_values = pts[nbr - 1].values; // Previous point
  116. var ante_values = pts[nbr - 2].values; // Point before the previous one
  117. var prev_x = prev_values[prev_values.length - 2];
  118. var prev_y = prev_values[prev_values.length - 1];
  119. var ante_x = ante_values[ante_values.length - 2];
  120. var ante_y = ante_values[ante_values.length - 1];
  121. //We don't want to add the same point twice consecutively
  122. if ((prev_x == x && prev_y == y)
  123. || (ante_x == x && ante_y == y)) return;
  124. var vectx = x - ante_x,
  125. vecty = y - ante_y;
  126. var norm = Math.hypot(vectx, vecty);
  127. var dist1 = dist(ante_x, ante_y, prev_x, prev_y) / norm,
  128. dist2 = dist(x, y, prev_x, prev_y) / norm;
  129. vectx /= ANGULARITY;
  130. vecty /= ANGULARITY;
  131. //Create 2 control points around the last point
  132. var cx1 = prev_x - dist1 * vectx,
  133. cy1 = prev_y - dist1 * vecty, //First control point
  134. cx2 = prev_x + dist2 * vectx,
  135. cy2 = prev_y + dist2 * vecty; //Second control point
  136. prev_values[2] = cx1;
  137. prev_values[3] = cy1;
  138. npoint = {
  139. type: "C", values: [
  140. cx2, cy2,
  141. x, y,
  142. x, y,
  143. ]
  144. };
  145. }
  146. pts.push(npoint);
  147. line.setPathData(pts);
  148. }
  149. function createLine(lineData) {
  150. //Creates a new line on the canvas, or update a line that already exists with new information
  151. var line = svg.getElementById(lineData.id) || Tools.createSVGElement("path");
  152. line.id = lineData.id;
  153. //If some data is not provided, choose default value. The line may be updated later
  154. line.setAttribute("stroke", lineData.color || "black");
  155. line.setAttribute("stroke-width", lineData.size || 10);
  156. svg.appendChild(line);
  157. return line;
  158. }
  159. Tools.add({ //The new tool
  160. "name": "Pencil",
  161. "icon": "✏",
  162. "listeners": {
  163. "press": startLine,
  164. "move": continueLine,
  165. "release": stopLine,
  166. },
  167. "draw": draw,
  168. "mouseCursor": "crosshair",
  169. "stylesheet": "tools/pencil/pencil.css"
  170. });
  171. //The pencil tool is selected by default
  172. Tools.change("Pencil");
  173. })(); //End of code isolation