signBoard.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /**
  2. * 签名版,钢笔效果
  3. * 源参考:https://www.cnblogs.com/fangsmile/p/14324460.html
  4. */
  5. class Point {
  6. constructor(x, y, time) {
  7. this.x = x;
  8. this.y = y;
  9. this.isControl = false;
  10. this.time = Date.now();
  11. this.lineWidth = 0;
  12. this.isAdd = false;
  13. }
  14. }
  15. class Line {
  16. constructor() {
  17. this.points = new Array();
  18. this.changeWidthCount = 0;
  19. this.lineWidth = 10;
  20. }
  21. }
  22. class HandwritingSelf {
  23. constructor(canvas,w,h,line_w=8,line_color='#ff0000') {
  24. this.canvas = {width:w,height:h};
  25. this.ctx = canvas
  26. var context = this.ctx;
  27. this.ctx.ellipse = function( x, y, a, b){
  28. // ----
  29. }
  30. // this.points = new Array();
  31. this.line = new Line();
  32. this.pointLines = new Array();//Line数组
  33. this.k = 0.5;
  34. this.begin = null;
  35. this.middle = null;
  36. this.end = null;
  37. this.preTime = null;
  38. this.lineWidth = line_w;
  39. this.lineColor = line_color;
  40. this.isDown = false;
  41. }
  42. down(x, y) {
  43. this.isDown = true;
  44. this.line = new Line();
  45. this.line.lineWidth = this.lineWidth;
  46. let currentPoint = new Point(x, y, Date.now());
  47. this.addPoint(currentPoint);
  48. this.preTime = Date.now();
  49. }
  50. move(x, y) {
  51. // console.log("move:",x,y)
  52. if (this.isDown) {
  53. let currentPoint = new Point(x, y, Date.now())
  54. this.addPoint(currentPoint);
  55. this.draw();
  56. }
  57. }
  58. up(x, y) {
  59. // if (e.touches.length > 0) {
  60. let currentPoint = new Point(x, y, Date.now())
  61. this.addPoint(currentPoint);
  62. // }
  63. this.draw(true);
  64. this.pointLines.push(this.line);
  65. this.begin = null;
  66. this.middle = null;
  67. this.end = null;
  68. this.isDown = false;
  69. }
  70. draw(isUp = false) {
  71. // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  72. this.ctx.setStrokeStyle(this.lineColor)
  73. //绘制不包含this.line的线条
  74. this.pointLines.forEach((line, index) => {
  75. let points = line.points;
  76. this.ctx.beginPath();
  77. this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
  78. this.ctx.fill();
  79. this.ctx.beginPath();
  80. this.ctx.moveTo(points[0].x, points[0].y);
  81. let lastW = line.lineWidth;
  82. this.ctx.setLineWidth(line.lineWidth);
  83. this.ctx.setLineJoin("round");
  84. this.ctx.setLineCap( "round");
  85. let minLineW = line.lineWidth / 4;
  86. let isChangeW = false;
  87. let changeWidthCount = line.changeWidthCount;
  88. for (let i = 1; i <= points.length; i++) {
  89. if (i == points.length) {
  90. this.ctx.stroke();
  91. break;
  92. }
  93. if (i > points.length - changeWidthCount) {
  94. if (!isChangeW) {
  95. this.ctx.stroke();//将之前的线条不变的path绘制完
  96. isChangeW = true;
  97. if (i > 1 && points[i - 1].isControl)
  98. continue;
  99. }
  100. let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
  101. points[i - 1].lineWidth = w;
  102. this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
  103. // this.ctx.strokeStyle = "rgba("+Math.random()*255+","+Math.random()*255+","+Math.random()*255+",1)";
  104. this.ctx.setLineWidth(w);
  105. this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
  106. this.ctx.lineTo(points[i].x, points[i].y);
  107. this.ctx.stroke();//将之前的线条不变的path绘制完
  108. } else {
  109. if (points[i].isControl && points[i + 1]) {
  110. this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
  111. } else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
  112. } else
  113. this.ctx.lineTo(points[i].x, points[i].y);
  114. }
  115. }
  116. })
  117. //绘制this.line线条
  118. let points;
  119. if (isUp)
  120. points = this.line.points;
  121. else
  122. points = [...this.line.points];
  123. //当前绘制的线条最后几个补点 贝塞尔方式增加点
  124. let count = 0;
  125. let insertCount = 0;
  126. let i = points.length - 1;
  127. let endPoint = points[i];
  128. let controlPoint;
  129. let startPoint;
  130. while (i >= 0) {
  131. if (points[i].isControl == true) {
  132. controlPoint = points[i];
  133. count++;
  134. } else {
  135. startPoint = points[i];
  136. }
  137. if (startPoint && controlPoint && endPoint) {//使用贝塞尔计算补点
  138. let dis = this.z_distance(startPoint, controlPoint) + this.z_distance(controlPoint, endPoint);
  139. let insertPoints = this.BezierCalculate([startPoint, controlPoint, endPoint], Math.floor(dis / 6) + 1);
  140. insertCount += insertPoints.length;
  141. var index = i;//插入位置
  142. // 把insertPoints 变成一个适合splice的数组(包含splice前2个参数的数组)
  143. insertPoints.unshift(index, 1);
  144. Array.prototype.splice.apply(points, insertPoints);
  145. //补完点后
  146. endPoint = startPoint;
  147. startPoint = null;
  148. }
  149. if (count >= 6)
  150. break;
  151. i--;
  152. }
  153. //确定最后线宽变化的点数
  154. let changeWidthCount = count + insertCount;
  155. if (isUp)
  156. this.line.changeWidthCount = changeWidthCount;
  157. //制造椭圆头
  158. this.ctx.fillStyle = "rgba(255,20,87,1)"
  159. this.ctx.beginPath();
  160. this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
  161. this.ctx.fill();
  162. this.ctx.draw(true);
  163. this.ctx.beginPath();
  164. this.ctx.moveTo(points[0].x, points[0].y);
  165. let lastW = this.line.lineWidth;
  166. this.ctx.setLineWidth(this.line.lineWidth);
  167. this.ctx.setLineJoin("round");
  168. this.ctx.setLineCap( "round");
  169. let minLineW = this.line.lineWidth / 4;
  170. let isChangeW = false;
  171. for (let i = 1; i <= points.length; i++) {
  172. if (i == points.length) {
  173. this.ctx.stroke();
  174. break;
  175. }
  176. //最后的一些点线宽变细
  177. if (i > points.length - changeWidthCount) {
  178. if (!isChangeW) {
  179. this.ctx.stroke();//将之前的线条不变的path绘制完
  180. isChangeW = true;
  181. if (i > 1 && points[i - 1].isControl)
  182. continue;
  183. }
  184. //计算线宽
  185. let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
  186. points[i - 1].lineWidth = w;
  187. this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
  188. // this.ctx.strokeStyle = "rgba(" + Math.random() * 255 + "," + Math.random() * 255 + "," + Math.random() * 255 + ",0.5)";
  189. this.ctx.setLineWidth(w);
  190. this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
  191. this.ctx.lineTo(points[i].x, points[i].y);
  192. this.ctx.stroke();//将之前的线条不变的path绘制完
  193. } else {
  194. if (points[i].isControl && points[i + 1]) {
  195. this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
  196. } else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
  197. } else
  198. this.ctx.lineTo(points[i].x, points[i].y);
  199. }
  200. }
  201. this.ctx.draw(true);
  202. }
  203. addPoint(p) {
  204. if (this.line.points.length >= 1) {
  205. let last_point = this.line.points[this.line.points.length - 1]
  206. let distance = this.z_distance(p, last_point);
  207. if (distance < 10) {
  208. return;
  209. }
  210. }
  211. if (this.line.points.length == 0) {
  212. this.begin = p;
  213. p.isControl = true;
  214. this.pushPoint(p);
  215. } else {
  216. this.middle = p;
  217. let controlPs = this.computeControlPoints(this.k, this.begin, this.middle, null);
  218. this.pushPoint(controlPs.first);
  219. this.pushPoint(p);
  220. p.isControl = true;
  221. this.begin = this.middle;
  222. }
  223. }
  224. addOtherPoint(p1, p2, w1, w2) {
  225. let otherPoints = new Array();
  226. let dis = this.z_distance(p1, p2);
  227. if (dis >= 25) {
  228. otherPoints.push(p1);
  229. let insertPCount = Math.floor(dis / 20);
  230. for (let j = 0; j < insertPCount; j++) {
  231. let insertP = new Point(p1.x + (j + 1) / (insertPCount + 1) * (p2.x - p1.x), p1.y + (j + 1) / (insertPCount + 1) * (p2.y - p1.y))
  232. insertP.isAdd = true;
  233. otherPoints.push(insertP);
  234. }
  235. otherPoints.push(p2);
  236. }
  237. let count = otherPoints.length;
  238. if (count > 0) {
  239. console.log("addOtherPoint")
  240. debugger
  241. let diffW = (w2 - w1) / (count - 1);
  242. for (let i = 1; i < count; i++) {
  243. let w = w1 + diffW * i;
  244. this.ctx.beginPath();
  245. this.ctx.setLineWidth(w);
  246. this.ctx.moveTo(otherPoints[i - 1].x, otherPoints[i - 1].y);
  247. this.ctx.lineTo(otherPoints[i].x, otherPoints[i].y)
  248. this.ctx.stroke();
  249. }
  250. }
  251. return otherPoints
  252. }
  253. pushPoint(p) {
  254. //排除重复点
  255. if (this.line.points.length >= 1 && this.line.points[this.line.points.length - 1].x == p.x && this.line.points[this.line.points.length - 1].y == p.y)
  256. return;
  257. this.line.points.push(p);
  258. }
  259. computeControlPoints(k, begin, middle, end) {
  260. if (k > 0.5 || k <= 0)
  261. return;
  262. let diff1 = new Point(middle.x - begin.x, middle.y - begin.y)
  263. let diff2 = null;
  264. if (end)
  265. diff2 = new Point(end.x - middle.x, end.y - middle.y)
  266. // let l1 = (diff1.x ** 2 + diff1.y ** 2) ** (1 / 2)
  267. // let l2 = (diff2.x ** 2 + diff2.y ** 2) ** (1 / 2)
  268. let first = new Point(middle.x - (k * diff1.x), middle.y - (k * diff1.y))
  269. let second = null;
  270. if (diff2)
  271. second = new Point(middle.x + (k * diff2.x), middle.y + (k * diff2.y))
  272. return { first: first, second: second }
  273. }
  274. // W_current =
  275. //   W_previous + min( abs(k*s - W_previous), distance * K_width_unit_change) (k * s-W_previous) >= 0
  276. //   W_previous - min( abs(k*s - W_previous), distance * K_width_unit_change) (k * s-W_previous) < 0
  277. //   W_current      当前线段的宽度
  278. //   W_previous    与当前线条相邻的前一条线段的宽度
  279. //   distance       当前线条的长度
  280. //   w_k         设定的一个固定阈值,表示:单位距离内, 笔迹的线条宽度可以变化的最大量.
  281. //   distance * w_k    即为当前线段的长度内, 笔宽可以相对于前一条线段笔宽的基础上, 最多能够变宽或者可以变窄多少.
  282. z_linewidth(b, e, bwidth, step) {
  283. if (e.time == b.time)
  284. return bwidth;
  285. let max_speed = 2.0;
  286. let d = this.z_distance(b, e);
  287. let s = d / (e.time - b.time);//计算速度
  288. console.log("s", e.time - b.time, s)
  289. s = s > max_speed ? max_speed : s;
  290. // let w = (max_speed - s) / max_speed;
  291. let w = 0.5 / s;
  292. let max_dif = d * step;
  293. console.log(w, bwidth, max_dif)
  294. if (w < 0.05) w = 0.05;
  295. if (Math.abs(w - bwidth) > max_dif) {
  296. if (w > bwidth)
  297. w = bwidth + max_dif;
  298. else
  299. w = bwidth - max_dif;
  300. }
  301. // printf("d:%.4f, time_diff:%lld, speed:%.4f, width:%.4f\n", d, e.t-b.t, s, w);
  302. return w;
  303. }
  304. z_distance(b, e) {
  305. return Math.sqrt(Math.pow(e.x - b.x, 2) + Math.pow(e.y - b.y, 2));
  306. }
  307. BezierCalculate(poss, precision) {
  308. //维度,坐标轴数(二维坐标,三维坐标...)
  309. let dimersion = 2;
  310. //贝塞尔曲线控制点数(阶数)
  311. let number = poss.length;
  312. //控制点数不小于 2 ,至少为二维坐标系
  313. if (number < 2 || dimersion < 2)
  314. return null;
  315. let result = new Array();
  316. //计算杨辉三角
  317. let mi = new Array();
  318. mi[0] = mi[1] = 1;
  319. for (let i = 3; i <= number; i++) {
  320. let t = new Array();
  321. for (let j = 0; j < i - 1; j++) {
  322. t[j] = mi[j];
  323. }
  324. mi[0] = mi[i - 1] = 1;
  325. for (let j = 0; j < i - 2; j++) {
  326. mi[j + 1] = t[j] + t[j + 1];
  327. }
  328. }
  329. //计算坐标点
  330. for (let i = 0; i < precision; i++) {
  331. let t = i / precision;
  332. let p = new Point(0, 0);
  333. p.isAdd = true;
  334. result.push(p);
  335. for (let j = 0; j < dimersion; j++) {
  336. let temp = 0.0;
  337. for (let k = 0; k < number; k++) {
  338. temp += Math.pow(1 - t, number - k - 1) * (j == 0 ? poss[k].x : poss[k].y) * Math.pow(t, k) * mi[k];
  339. }
  340. j == 0 ? p.x = temp : p.y = temp;
  341. }
  342. }
  343. return result;
  344. }
  345. }
  346. export default HandwritingSelf;