canvasWrite.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. import toast from '../miniprogram_npm/@vant/weapp/toast/toast';
  2. var b5_width = 176; // B5 纸的码点宽度
  3. var b5_height = 250; // B5纸的码点高度
  4. var x_codepoint_size = 1.524; // 横坐标码点的大小
  5. var y_codepoint_size = 1.524; // 纵坐标码点的大小
  6. let ax = b5_width / x_codepoint_size
  7. let ay = b5_height / y_codepoint_size
  8. const event = require('../tqlsdk/event')
  9. class myCanvas {
  10. constructor(canvasId, canvasWidth, canvasHeight, imgUrl) {
  11. this.canvas = null
  12. this.canvasNode = null
  13. this.canvas = null
  14. this.canvasNode = null
  15. this.ctx = null
  16. this.allImage = {}
  17. this.force = 0
  18. this.canvasWidth = canvasWidth
  19. this.canvasHeight = canvasHeight
  20. this.mapData = new Map();
  21. this.x_coordinate = null
  22. this.y_coordinate = null
  23. this.color = '#141414'
  24. this.isDown = false
  25. this.lastPage = null
  26. this.initCanvas(canvasId, canvasWidth, canvasHeight, imgUrl)
  27. this.isReplay = false
  28. this.p_index = 0
  29. this.scale = 1
  30. this.offsetX = 0
  31. this.offsetY = 0
  32. this.lineWidth = 2
  33. this.penWidth = 0.5
  34. this.strokeFlag = true
  35. this.lastPoint = null
  36. this.imgUrls = null
  37. this.offlineFlag = false
  38. this.readLocalFileFlag = false
  39. }
  40. // 开关笔锋
  41. /**
  42. *
  43. * @param {boolean} flag
  44. */
  45. changeStroke(flag) {
  46. this.strokeFlag = flag
  47. }
  48. // 改变笔迹宽度
  49. /**
  50. *
  51. * @param {number} width
  52. */
  53. changeWidth(width) {
  54. this.penWidth = width
  55. }
  56. // 初始化canvas
  57. /**
  58. *
  59. * @param {string} canvasId
  60. * @param {number} canvasWidth
  61. * @param {number} canvasHeight
  62. * @param {Array} imgUrl
  63. */
  64. async initCanvas(canvasId, canvasWidth, canvasHeight, imgUrl) {
  65. this.canvasNode = await new Promise((resolve, reject) => {
  66. const query = wx.createSelectorQuery()
  67. query.select(canvasId).fields({
  68. node: true,
  69. size: true
  70. }).exec(res => {
  71. resolve(res[0])
  72. })
  73. })
  74. this.canvas = this.canvasNode.node
  75. this.ctx = this.canvas.getContext('2d')
  76. // 以下为canvas的缩放比例 默认为2倍 如有其它需求 请自行更改
  77. this.canvas.width = canvasWidth * 2
  78. this.canvas.height = canvasHeight * 2
  79. this.imgUrls = imgUrl
  80. this.ctx.lineCap = "round"
  81. this.ctx.lineJoin = "miter"
  82. this.ctx.miterLimit = 1
  83. }
  84. // 改变颜色
  85. /**
  86. *
  87. * @param {string} color
  88. */
  89. changeColor(color) {
  90. this.color = color
  91. this.ctx.fillStyle = color;
  92. this.ctx.strokeStyle = color;
  93. }
  94. // 绘制图片
  95. /**
  96. *
  97. * @param {string} imgUrl
  98. * @param {number} x1
  99. * @param {number} y1
  100. * @param {number} x2
  101. * @param {number} y2
  102. */
  103. async drawImage(imgUrl, x1, y1, x2, y2) {
  104. if (!this.allImage[imgUrl]) {
  105. this.allImage[imgUrl] = await new Promise((resolve) => {
  106. const img = this.canvas.createImage()
  107. img.src = imgUrl
  108. img.onload = () => {
  109. resolve(img)
  110. }
  111. })
  112. }
  113. this.ctx.drawImage(this.allImage[imgUrl], x1, y1, x2, y2)
  114. }
  115. // 切页
  116. /**
  117. *
  118. * @param {number} index
  119. */
  120. async changePage(index) {
  121. if (index === this.lastPage && !this.isReplay) {
  122. return
  123. }
  124. // 此处切页
  125. event.emit('changePage', index)
  126. this.lastPage = index
  127. await this.drawImage(this.imgUrls[(index & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  128. // 切页绘制
  129. if (!this.isReplay) {
  130. let pageData = this.mapData.get(index)
  131. let pageLength = pageData.length
  132. for (let i = 0; i < pageLength; i++) {
  133. this.strokeFlag ? this.strokeType(pageData[i].dotType, pageData[i].xPoint, pageData[i].yPoint, pageData[i].force) : this.penType(pageData[i].dotType, pageData[i].xPoint, pageData[i].yPoint)
  134. }
  135. }
  136. }
  137. // 清除
  138. async clear() {
  139. this.mapData.clear();
  140. this.ctx.clearRect(0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  141. // 清除后重新绘制底图
  142. this.addPageId(this.lastPage)
  143. await this.drawImage(this.imgUrls[(this.lastPage & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  144. }
  145. // 回放
  146. async replay() {
  147. this.isReplay = true
  148. this.ctx.clearRect(0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  149. // 清除后重新绘制底图
  150. this.drawImage(this.imgUrls[(this.lastPage & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  151. if (!this.mapData.has(this.lastPage) || this.mapData.get(this.lastPage).length === 0) {
  152. this.isReplay = false
  153. return
  154. }
  155. let currentPageData = this.mapData.get(this.lastPage)
  156. let dataLength = currentPageData.length
  157. for (let index = 0; index < dataLength; index++) {
  158. await sleep(10)
  159. this.strokeFlag ? this.strokeType(currentPageData[index].dotType, currentPageData[index].xPoint, currentPageData[index].yPoint, currentPageData[index].force) : this.penType(currentPageData[index].dotType, currentPageData[index].xPoint, currentPageData[index].yPoint)
  160. }
  161. this.isReplay = false
  162. }
  163. // 储存数据
  164. addPageId(myPageId) {
  165. if (myPageId === this.lastPage) {
  166. !this.mapData.has(myPageId) && this.mapData.set(myPageId, [])
  167. return
  168. } !this.mapData.has(myPageId) && this.mapData.set(myPageId, [])
  169. this.changePage(myPageId);
  170. }
  171. // 四舍五入算法
  172. roundNum(number, fractionDigits) {
  173. return Math.round(number * Math.pow(10, fractionDigits)) / Math.pow(10, fractionDigits);
  174. }
  175. // 数据处理
  176. usbData(data) {
  177. // 回放时不接收数据
  178. if (this.isReplay) {
  179. toast.fail('回放时请不要书写')
  180. return
  181. }
  182. if (data.dotType === "PEN_DOWN") {
  183. // 切页
  184. this.addPageId(data.pageID)
  185. }
  186. // switch (data.bookID) {
  187. // case value:
  188. // break;
  189. // default:
  190. // break;
  191. // }
  192. console.log((data.x) + ((data.fx) / 100))
  193. console.log((data.y) + ((data.fy) / 100))
  194. let x = (data.x - 10) + data.fx / 100
  195. let y = (data.y - 10) + data.fy / 100
  196. // 得到的x,y坐标 因为此处canvas为2倍放大 所以图标宽度也需要乘以2
  197. let xPoint = this.roundNum(((((data.x) + ((data.fx) / 100)) * this.canvasWidth * 2) / ax), 13);
  198. let yPoint = this.roundNum(((((data.y) + ((data.fy) / 100)) * this.canvasHeight * 2) / ay), 13);
  199. this.mapData.get(data.pageID).push({
  200. bookId: data.bookID,
  201. pageID: data.pageID,
  202. timeLog: data.timeLong,
  203. xPoint,
  204. yPoint,
  205. dotType: data.dotType,
  206. force: data.force,
  207. color: this.color
  208. })
  209. this.strokeFlag ? this.strokeType(data.dotType, xPoint, yPoint, data.force) : this.penType(data.dotType, xPoint, yPoint)
  210. }
  211. // 点分类型(有笔锋)
  212. strokeType(dotType, x, y, force) {
  213. console.log(x, y, force);
  214. dotType === 'PEN_DOWN' ? this.strokeDown(x, y, force, this.penWidth) : dotType === 'PEN_MOVE' ? this.strokeMove(x, y, force, this.penWidth) : this.strokeUp(x, y, force, this.ctx, this.penWidth)
  215. }
  216. // 点分类型(无笔锋)
  217. penType(dotType, x, y) {
  218. dotType === 'PEN_DOWN' ? this.penDown(x, y) : dotType === 'PEN_MOVE' ? this.penMove(x, y) : this.penUp(x, y)
  219. }
  220. // down点方法(无笔锋)
  221. penDown(x, y) {
  222. this.lastPoint = {
  223. x,
  224. y
  225. }
  226. }
  227. // move点方法(无笔锋)
  228. penMove(x, y) {
  229. let newPoint = {
  230. x,
  231. y
  232. }
  233. this.drawLine(this.lastPoint.x, this.lastPoint.y, newPoint.x, newPoint.y);
  234. this.lastPoint = newPoint;
  235. }
  236. // up点方法
  237. penUp(x_0, y_0) {
  238. this.drawLine(this.lastPoint.x, this.lastPoint.y, x_0, y_0)
  239. }
  240. // 绘制方法(无笔锋)
  241. drawLine(x1, y1, x2, y2) {
  242. this.ctx.beginPath();
  243. this.ctx.moveTo(x1 * 0.97, y1 * 0.976);
  244. this.ctx.lineWidth = this.penWidth * 2
  245. this.ctx.lineTo(x2 * 0.97, y2 * 0.976);
  246. this.ctx.fill();
  247. this.ctx.stroke();
  248. this.ctx.closePath();
  249. }
  250. // down点方法(有笔锋)
  251. strokeDown(x, y, force, width) {
  252. console.log(123)
  253. penStrokeDown(x, y, force, width)
  254. }
  255. // move点方法(有笔锋)
  256. strokeMove(x, y, force, width) {
  257. penStrokeMove(x, y, force, width)
  258. }
  259. // up点方法(有笔锋)
  260. strokeUp(x, y, force, ctx, penWidth) {
  261. penStrokeUp(x, y, force, ctx, penWidth)
  262. }
  263. }
  264. // 控制点类
  265. class ControlPoint {
  266. constructor(x, y, width) {
  267. this.x = x
  268. this.y = y
  269. this.width = width
  270. }
  271. set(x, y, width) {
  272. this.x = x
  273. this.y = y
  274. this.width = width
  275. }
  276. setControlPoint(point) {
  277. this.x = point.x
  278. this.y = point.y
  279. this.width = point.width
  280. }
  281. }
  282. // 贝塞尔类
  283. class Bezier {
  284. constructor() {
  285. // 控制点
  286. this.mControl = new ControlPoint();
  287. // 距离
  288. this.mDestination = new ControlPoint()
  289. // 下一个需要控制点
  290. this.mNextControl = new ControlPoint()
  291. // 资源的点
  292. this.mSource = new ControlPoint()
  293. }
  294. initPoint(last, cur) {
  295. this.init(last.x, last.y, last.width, cur.x, cur.y, cur.width)
  296. }
  297. init(lastX, lastY, lastWidth, x, y, width) {
  298. //资源点设置 最后的点位资源点
  299. this.mSource.set(lastX, lastY, lastWidth)
  300. let xMid = getMid(lastX, x)
  301. let yMid = getMid(lastY, y)
  302. let wMid = getMid(lastWidth, width)
  303. // 距离单为平均点
  304. this.mDestination.set(xMid, yMid, wMid)
  305. // 控制点为当前的距离点
  306. this.mControl.set(getMid(lastX, xMid), getMid(lastY, yMid), getMid(lastWidth, wMid))
  307. // 下个控制点为当前点
  308. this.mNextControl.set(x, y, width)
  309. }
  310. addNodePoint(cur) {
  311. this.addNode(cur.x, cur.y, cur.width)
  312. }
  313. //替换旧的点 原来的距离点变换为资源点,控制点变为原来的下一个控制点,距离点取原来控制点的和新的的一半 下个控制点为新的点
  314. addNode(x, y, width) {
  315. this.mSource.setControlPoint(this.mDestination)
  316. this.mControl.setControlPoint(this.mNextControl)
  317. this.mDestination.set(getMid(this.mNextControl.x, x), getMid(this.mNextControl.y, y), getMid(this.mNextControl.width, width))
  318. this.mNextControl.set(x, y, width);
  319. }
  320. //结束时改变点的位置
  321. penEnd() {
  322. this.mSource.setControlPoint(this.mDestination)
  323. this.mControl.set(getMid(this.mNextControl.x, this.mSource.x), getMid(this.mNextControl.y, this.mSource.y), getMid(this.mNextControl.width, this.mSource.width))
  324. this.mDestination.setControlPoint(this.mNextControl)
  325. }
  326. // 获取点的信息
  327. getPoint(t) {
  328. let point = new ControlPoint()
  329. point.set(this.getX(t), this.getY(t), this.getW(t))
  330. return point
  331. }
  332. //三阶曲线控制点
  333. getValue(p0, p1, p2, t) {
  334. let a = p2 - 2 * p1 + p0
  335. let b = 2 * (p1 - p0)
  336. return a * t * t + b * t + p0
  337. }
  338. getX(t) {
  339. return this.getValue(this.mSource.x, this.mControl.x, this.mDestination.x, t)
  340. }
  341. getY(t) {
  342. return this.getValue(this.mSource.y, this.mControl.y, this.mDestination.y, t)
  343. }
  344. getW(t) {
  345. return getWidth(this.mSource.width, this.mDestination.width, t)
  346. }
  347. }
  348. // 计算压力值
  349. const calculatePressure = force => force >= 0 && force <= 20 ? 40 : force > 20 && force <= 40 ? 60 : force > 40 && force <= 60 ? 80 : force > 60 && force <= 90 ? 100 : force > 90 && force <= 150 ? 120 : 130;
  350. // 获取中间值
  351. const getMid = (x, y) => (x + y) / 2
  352. // 计算宽度
  353. const getWidth = (w0, w1, t) => w0 + (w1 - w0) * t
  354. // 当前点
  355. let curPoint = null
  356. // 上一个点
  357. let mLastPoint = null
  358. // 计算出来的线段宽度
  359. let mLastWidth = null
  360. // 贝塞尔类实例
  361. let mBezier = new Bezier()
  362. // 笔画的第一点
  363. let mFirstPoint = null
  364. // 点击数
  365. let pointNum = 0
  366. //转换参数
  367. const transFormScale = 80
  368. // 每笔的数据
  369. let nowList = []
  370. // 上一压力值
  371. let lastForce = null
  372. // down点(笔锋)
  373. const penStrokeDown = (x, y, force, width) => {
  374. let pressure = calculatePressure(force)
  375. mLastWidth = pressure / transFormScale * width
  376. pointNum = 1
  377. // 记录down点信息
  378. curPoint = new ControlPoint(x, y, mLastWidth)
  379. mLastPoint = new ControlPoint(x, y, mLastWidth)
  380. console.log(mLastPoint)
  381. mFirstPoint = new ControlPoint(x, y, mLastWidth)
  382. nowList = []
  383. nowList.push(curPoint)
  384. lastForce = force
  385. }
  386. // move点方法(笔锋)
  387. const penStrokeMove = (x, y, force, penWidth) => {
  388. let pressure = calculatePressure(force)
  389. let pressureCheck = forceCreck(lastForce, pressure)
  390. lastForce = pressureCheck
  391. let curWidth = pressureCheck / transFormScale * penWidth
  392. curPoint = new ControlPoint(x, y, curWidth)
  393. console.log(curPoint, mLastPoint)
  394. let curDis = Math.hypot(curPoint.x - mLastPoint.x, curPoint.y - mLastPoint.y)
  395. if (pointNum === 1) {
  396. pointNum++;
  397. mBezier.initPoint(mLastPoint, curPoint);
  398. } else {
  399. mBezier.addNodePoint(curPoint);
  400. }
  401. mLastWidth = curWidth;
  402. doMove(curDis);
  403. mLastWidth = new ControlPoint(curPoint.x, curPoint.y, curPoint.width);
  404. console.log(mFirstPoint);
  405. }
  406. // up点方法(笔锋)
  407. const penStrokeUp = (x, y, force, context, penWidth) => {
  408. if (nowList.length === 0) {
  409. return
  410. }
  411. curPoint = new ControlPoint(x, y, 0)
  412. let deltaX = curPoint.x - mLastPoint.x
  413. let deltaY = curPoint.y - mLastPoint.y
  414. let curDis = Math.hypot(deltaX, deltaY)
  415. mBezier.addNodePoint(curPoint)
  416. let steps = 1 + Math.floor((curDis / 10))
  417. let step = 1 / steps
  418. for (let t = 0; t < 1; t += step) {
  419. let point = mBezier.getPoint(t)
  420. nowList.push(point)
  421. }
  422. mBezier.penEnd()
  423. for (let t = 0; t < 1; t += step) {
  424. let point = mBezier.getPoint(t)
  425. nowList.push(point)
  426. }
  427. draws(context, penWidth);
  428. nowList = []
  429. }
  430. function doMove(curDis) {
  431. let steps = 1 + Math.floor((curDis / 10));
  432. let step = 1 / steps
  433. for (let t = 0; t < 1; t += step) {
  434. let Point = mBezier.getPoint(t)
  435. nowList.push(Point)
  436. }
  437. }
  438. function draws(context, penWidth) {
  439. doPreDraw(context, penWidth);
  440. }
  441. function doPreDraw(context, penWidth) {
  442. let curPoint = nowList[0]
  443. let length = nowList.length
  444. for (let i = 1; i < length; i++) {
  445. drawPoint(curPoint, nowList[i], context, penWidth)
  446. curPoint = nowList[i]
  447. }
  448. }
  449. function drawPoint(curPoint, point, context, penWidth) {
  450. // 相同点不绘制
  451. if (curPoint.x === point.x && curPoint.y === point.y) {
  452. return
  453. }
  454. drawLine(curPoint.x, curPoint.y, curPoint.width, point.x, point.y, point.width, context, penWidth);
  455. }
  456. // 绘制方法
  457. function drawLine(x0, y0, w0, x1, y1, w1, context, penWidth) {
  458. let curDis = Math.hypot(x1 - x0, y1 - y0)
  459. let step = 1
  460. if (penWidth <= 6) {
  461. step = 1 + Math.floor((curDis))
  462. } else if (penWidth > 60) {
  463. step = 1 + Math.floor((curDis / 4))
  464. } else {
  465. step = 1 + Math.floor((curDis / 3))
  466. }
  467. let deltaX = (x1 - x0) / step
  468. let deltaY = (y1 - y0) / step
  469. let deltaW = (w1 - w0) / step
  470. let x = x0
  471. let y = y0
  472. let w = w0
  473. for (let i = 0; i < step; i++) {
  474. let left = x + w / 2
  475. let top = y + w / 2
  476. let right = x - w / 2
  477. let bottom = y - w / 2
  478. let midPointX = (left + right) / 2
  479. let midPointY = (top + bottom) / 2
  480. let xRadius = Math.abs((left - right) / 2)
  481. let yRadius = Math.abs((top - bottom) / 2)
  482. // context.setLineDash([])
  483. context.beginPath();
  484. context.ellipse(midPointX * 0.97, midPointY * 0.976, xRadius, yRadius, 0, 0, Math.PI * 2);
  485. context.stroke();
  486. context.closePath();
  487. context.fill();
  488. x += deltaX
  489. y += deltaY
  490. w += deltaW
  491. }
  492. }
  493. // 压力值前后差距过大补正
  494. const forceCreck = (lastForce, curForce) => {
  495. if ((curForce - lastForce) > 35) {
  496. return (lastForce + curForce) / 2
  497. }
  498. return curForce
  499. }
  500. // 休眠方法
  501. function sleep(ms) {
  502. return new Promise(resolve => setTimeout(resolve, ms))
  503. }
  504. export {
  505. myCanvas
  506. }