canvasWrite_smoothing.js 20 KB

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