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 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. lastTime = performance.now(); //The time at which the last point was drawn
  30. //The data of the message that will be sent for every new point
  31. function PointMessage(x, y) {
  32. this.type = 'child';
  33. this.parent = curLineId;
  34. this.x = x;
  35. this.y = y;
  36. }
  37. function startLine(x, y, evt) {
  38. //Prevent the press from being interpreted by the browser
  39. evt.preventDefault();
  40. curLineId = Tools.generateUID("l"); //"l" for line
  41. Tools.drawAndSend({
  42. 'type': 'line',
  43. 'id': curLineId,
  44. 'color': Tools.getColor(),
  45. 'size': Tools.getSize(),
  46. 'opacity': Tools.getOpacity()
  47. });
  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 !== "" && performance.now() - lastTime > 70) {
  55. Tools.drawAndSend(new PointMessage(x, y));
  56. lastTime = performance.now();
  57. }
  58. if (evt) evt.preventDefault();
  59. }
  60. function stopLine(x, y) {
  61. //Add a last point to the line
  62. continueLine(x, y);
  63. curLineId = "";
  64. }
  65. var renderingLine = {};
  66. function draw(data) {
  67. switch (data.type) {
  68. case "line":
  69. renderingLine = createLine(data);
  70. break;
  71. case "child":
  72. var line = (renderingLine.id == data.parent) ? renderingLine : svg.getElementById(data.parent);
  73. if (!line) {
  74. console.error("Pencil: Hmmm... I received a point of a line that has not been created (%s).", data.parent);
  75. line = renderingLine = createLine({ "id": data.parent }); //create a new line in order not to loose the points
  76. }
  77. addPoint(line, data.x, data.y);
  78. break;
  79. case "endline":
  80. //TODO?
  81. break;
  82. default:
  83. console.error("Pencil: Draw instruction with unknown type. ", data);
  84. break;
  85. }
  86. }
  87. function dist(x1, y1, x2, y2) {
  88. //Returns the distance between (x1,y1) and (x2,y2)
  89. return Math.hypot(x2 - x1, y2 - y1);
  90. }
  91. var pathDataCache = {};
  92. function getPathData(line) {
  93. var pathData = pathDataCache[line.id];
  94. if (!pathData) {
  95. pathData = line.getPathData();
  96. pathDataCache[line.id] = pathData;
  97. }
  98. return pathData;
  99. }
  100. var svg = Tools.svg;
  101. function addPoint(line, x, y) {
  102. var pts = getPathData(line), //The points that are already in the line as a PathData
  103. nbr = pts.length; //The number of points already in the line
  104. switch (nbr) {
  105. case 0: //The first point in the line
  106. //If there is no point, we have to start the line with a moveTo statement
  107. npoint = { type: "M", values: [x, y] };
  108. break;
  109. case 1: //There is only one point.
  110. //Draw a curve that is segment between the old point and the new one
  111. npoint = {
  112. type: "C", values: [
  113. pts[0].values[0], pts[0].values[1],
  114. x, y,
  115. x, y,
  116. ]
  117. };
  118. break;
  119. default: //There are at least two points in the line
  120. //We add the new point, and smoothen the line
  121. var ANGULARITY = 3; //The lower this number, the smoother the line
  122. var prev_values = pts[nbr - 1].values; // Previous point
  123. var ante_values = pts[nbr - 2].values; // Point before the previous one
  124. var prev_x = prev_values[prev_values.length - 2];
  125. var prev_y = prev_values[prev_values.length - 1];
  126. var ante_x = ante_values[ante_values.length - 2];
  127. var ante_y = ante_values[ante_values.length - 1];
  128. //We don't want to add the same point twice consecutively
  129. if ((prev_x == x && prev_y == y)
  130. || (ante_x == x && ante_y == y)) return;
  131. var vectx = x - ante_x,
  132. vecty = y - ante_y;
  133. var norm = Math.hypot(vectx, vecty);
  134. var dist1 = dist(ante_x, ante_y, prev_x, prev_y) / norm,
  135. dist2 = dist(x, y, prev_x, prev_y) / norm;
  136. vectx /= ANGULARITY;
  137. vecty /= ANGULARITY;
  138. //Create 2 control points around the last point
  139. var cx1 = prev_x - dist1 * vectx,
  140. cy1 = prev_y - dist1 * vecty, //First control point
  141. cx2 = prev_x + dist2 * vectx,
  142. cy2 = prev_y + dist2 * vecty; //Second control point
  143. prev_values[2] = cx1;
  144. prev_values[3] = cy1;
  145. npoint = {
  146. type: "C", values: [
  147. cx2, cy2,
  148. x, y,
  149. x, y,
  150. ]
  151. };
  152. }
  153. pts.push(npoint);
  154. line.setPathData(pts);
  155. }
  156. function createLine(lineData) {
  157. //Creates a new line on the canvas, or update a line that already exists with new information
  158. var line = svg.getElementById(lineData.id) || Tools.createSVGElement("path");
  159. line.id = lineData.id;
  160. //If some data is not provided, choose default value. The line may be updated later
  161. line.setAttribute("stroke", lineData.color || "black");
  162. line.setAttribute("stroke-width", lineData.size || 10);
  163. line.setAttribute("opacity", Math.max(0.1, Math.min(1, lineData.opacity)) || 1);
  164. svg.appendChild(line);
  165. return line;
  166. }
  167. Tools.add({ //The new tool
  168. "name": "Pencil",
  169. "icon": "✏",
  170. "listeners": {
  171. "press": startLine,
  172. "move": continueLine,
  173. "release": stopLine,
  174. },
  175. "draw": draw,
  176. "mouseCursor": "crosshair",
  177. "stylesheet": "tools/pencil/pencil.css"
  178. });
  179. })(); //End of code isolation