stroke.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. var b5_width = 176; // B5 纸的码点宽度
  2. var b5_height = 250; // B5纸的码点高度
  3. var x_codepoint_size = 1.524; // 横坐标码点的大小
  4. var y_codepoint_size = 1.524; // 纵坐标码点的大小
  5. const ax = b5_width / x_codepoint_size
  6. const ay = b5_height / y_codepoint_size
  7. class Stroke {
  8. constructor(canvasId, canvasWidth, canvasHeight, imgUrls) {
  9. this.canvas = null
  10. this.canvasNode = null
  11. this.ctx = null
  12. this.allImage = {}
  13. this.force = 0
  14. this.canvasWidth = canvasWidth
  15. this.canvasHeight = canvasHeight
  16. this.mapData = new Map();
  17. this.x_coordinate = null
  18. this.y_coordinate = null
  19. this.color = '#000'
  20. this.isDown = false
  21. this.lastPage = null
  22. this.initCanvas(canvasId, canvasWidth, canvasHeight)
  23. this.isReplay = false
  24. this.p_index = 0
  25. this.scale = 1
  26. this.offsetX = 0
  27. this.offsetY = 0
  28. this.lineWidth = 2
  29. this.penWidth = 1.0
  30. this.currentX = 0
  31. this.currentY = 0
  32. this.currentMidX = 0
  33. this.currentMidY = 0
  34. this.preMidX = 0
  35. this.preMidY = 0
  36. this.preX = 0
  37. this.preY = 0
  38. this.lastPointX = 0
  39. this.lastPointY = 0
  40. this.imgUrls = imgUrls
  41. }
  42. // 初始化canvas
  43. async initCanvas(canvasId, canvasWidth, canvasHeight) {
  44. this.canvasNode = await new Promise((resolve, reject) => {
  45. const query = wx.createSelectorQuery()
  46. query.select(canvasId).fields({
  47. node: true,
  48. size: true
  49. }).exec(res => {
  50. resolve(res[0])
  51. })
  52. })
  53. this.canvas = this.canvasNode.node
  54. this.ctx = this.canvas.getContext('2d')
  55. // 以下为canvas的缩放比例 默认为2倍 如有其它需求 请自行更改
  56. this.canvas.width = canvasWidth * 2
  57. this.canvas.height = canvasHeight * 2
  58. this.ctx.lineCap = "round"
  59. this.ctx.lineJoin = "miter"
  60. this.ctx.miterLimit = 1
  61. }
  62. // 绘制图片
  63. async drawImage(imgUrl, x1, y1, x2, y2) {
  64. console.log(this)
  65. if (!this.allImage[imgUrl]) {
  66. this.allImage[imgUrl] = await new Promise((resolve) => {
  67. console.log(this.canvas)
  68. const img = this.canvas.createImage()
  69. img.src = imgUrl
  70. img.onload = () => {
  71. resolve(img)
  72. }
  73. })
  74. }
  75. this.ctx.drawImage(this.allImage[imgUrl], x1, y1, x2, y2)
  76. }
  77. // 切页
  78. changePage(index) {
  79. if (index === this.lastPage && !this.isReplay) {
  80. return
  81. }
  82. this.lastPage = index
  83. this.drawImage(this.imgUrls[(index & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2)
  84. }
  85. addPageId(myPageId) {
  86. if (myPageId === this.lastPage) {
  87. !this.mapData.get(myPageId) && this.mapData.set(myPageId, [])
  88. return
  89. }
  90. this.changePage(myPageId);
  91. !this.mapData.has(myPageId) && this.mapData.set(myPageId, [])
  92. this.mapData.get(myPageId).forEach((item) => {
  93. penWidth = this.setPenFront(item.force)
  94. this.penType(item.dotType, item.xPoint, item.yPoint, item.color)
  95. })
  96. }
  97. // 四舍五入算法
  98. roundNum(number, fractionDigits) {
  99. return Math.round(number * Math.pow(10, fractionDigits)) / Math.pow(10, fractionDigits);
  100. }
  101. // 数据处理
  102. usbData(data) {
  103. if (data.dotType === 'PEN_DOWN') {
  104. this.addPageId(data.pageID, data)
  105. }
  106. let xPoint = this.roundNum((((data.x + (data.fx / 100)) * this.canvasWidth * 2) / ax), 13);
  107. // console.log(this.canvasWidth)
  108. let yPoint = this.roundNum((((data.y + (data.fy / 100)) * this.canvasHeight * 2) / ay), 13);
  109. // console.log(xPoint, yPoint)
  110. this.penType(data.dotType, xPoint, yPoint, data.force);
  111. // console.log(xPoint, yPoint, data.force)
  112. }
  113. // 点分类型
  114. penType(dotType, x, y, force) {
  115. dotType === "PEN_DOWN" ? this.onDown(x, y, force) : dotType === "PEN_MOVE" ? this.onMove(x, y, force) : this.onUp(x, y, force, this.ctx)
  116. }
  117. // down点方法
  118. onDown(x, y, force) {
  119. penDown(x, y, force)
  120. }
  121. // move方法
  122. onMove(x, y, force) {
  123. penMove(x, y, force)
  124. }
  125. // up点方法
  126. onUp(x, y, force, context) {
  127. penUp(x, y, force, context)
  128. }
  129. }
  130. class ControlPoint {
  131. // x = null
  132. // y = null
  133. // alpha = 255
  134. // width = null
  135. constructor(x, y, width) {
  136. this.x = x
  137. this.y = y
  138. this.width = width
  139. }
  140. set(x, y, width) {
  141. this.x = x
  142. this.y = y
  143. this.width = width
  144. }
  145. setControlPoint(point) {
  146. this.x = point.x
  147. this.y = point.y
  148. this.width = point.width
  149. }
  150. }
  151. //贝塞尔类
  152. class Bezier {
  153. // // 控制点
  154. // mControl = new ControlPoint();
  155. // // 距离
  156. // mDestination = new ControlPoint()
  157. // // 下一个需要控制点
  158. // mNextControl = new ControlPoint()
  159. // // 资源的点
  160. // mSource = new ControlPoint()
  161. constructor() {
  162. // 控制点
  163. this.mControl = new ControlPoint();
  164. // 距离
  165. this.mDestination = new ControlPoint()
  166. // 下一个需要控制点
  167. this.mNextControl = new ControlPoint()
  168. // 资源的点
  169. this.mSource = new ControlPoint()
  170. }
  171. initPoint(last, cur) {
  172. this.init(last.x, last.y, last.width, cur.x, cur.y, cur.width)
  173. }
  174. init(lastX, lastY, lastWidth, x, y, width) {
  175. //资源点设置 最后的点位资源点
  176. this.mSource.set(lastX, lastY, lastWidth)
  177. let xMid = getMid(lastX, x)
  178. let yMid = getMid(lastY, y)
  179. let wMid = getMid(lastWidth, width)
  180. // 距离单为平均点
  181. this.mDestination.set(xMid, yMid, wMid)
  182. // 控制点为当前的距离点
  183. this.mControl.set(getMid(lastX, xMid), getMid(lastY, yMid), getMid(lastWidth, wMid))
  184. // 下个控制点为当前点
  185. this.mNextControl.set(x, y, width)
  186. }
  187. addNodePoint(cur) {
  188. this.addNode(cur.x, cur.y, cur.width)
  189. }
  190. //替换旧的点 原来的距离点变换为资源点,控制点变为原来的下一个控制点,距离点取原来控制点的和新的的一半 下个控制点为新的点
  191. addNode(x, y, width) {
  192. this.mSource.setControlPoint(this.mDestination)
  193. this.mControl.setControlPoint(this.mNextControl)
  194. this.mDestination.set(getMid(this.mNextControl.x, x), getMid(this.mNextControl.y, y), getMid(this.mNextControl.width, width))
  195. this.mNextControl.set(x, y, width);
  196. // console.log(this.mDestination,this.mNextControl,this.mSource,this.mControl)
  197. }
  198. //
  199. penEnd() {
  200. this.mSource.setControlPoint(this.mDestination)
  201. this.mControl.set(getMid(this.mNextControl.x, this.mSource.x), getMid(this.mNextControl.y, this.mSource.y), getMid(this.mNextControl.width, this.mSource.width))
  202. this.mDestination.setControlPoint(this.mNextControl)
  203. }
  204. // 获取点的信息
  205. getPoint(t) {
  206. let point = new ControlPoint()
  207. point.set(this.getX(t), this.getY(t), this.getW(t))
  208. return point
  209. }
  210. //三阶曲线控制点
  211. getValue(p0, p1, p2, t) {
  212. let a = p2 - 2 * p1 + p0
  213. let b = 2 * (p1 - p0)
  214. return a * t * t + b * t + p0
  215. }
  216. getX(t) {
  217. return this.getValue(this.mSource.x, this.mControl.x, this.mDestination.x, t)
  218. }
  219. getY(t) {
  220. return this.getValue(this.mSource.y, this.mControl.y, this.mDestination.y, t)
  221. }
  222. getW(t) {
  223. return getWidth(this.mSource.width, this.mDestination.width, t)
  224. }
  225. }
  226. function calculatePressure(force) {
  227. return 0
  228. return 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;
  229. }
  230. const getMid = (x, y) => (x + y) / 2
  231. const getWidth = (w0, w1, t) => w0 + (w1 - w0) * t
  232. // 当前点
  233. let curPoint = null
  234. //上一个点
  235. let mLastPoint = null
  236. // 计算出来的线段宽度
  237. let mLastWidth = null
  238. // 贝塞尔控制点
  239. let mBezier = new Bezier();
  240. // 笔画的第一点
  241. let mFirstPoint = null
  242. // 点个数
  243. let pointNum = 0
  244. const transFormScale = 80
  245. let penWidth = 1.6
  246. let nowList = [];
  247. let lastForce = null
  248. // down点方法
  249. const penDown = (x, y, force) => {
  250. let pressure = calculatePressure(force)
  251. mLastWidth = pressure / transFormScale * penWidth
  252. pointNum = 1
  253. // 记录down点信息
  254. curPoint = new ControlPoint(x, y, mLastWidth)
  255. mLastPoint = new ControlPoint(x, y, mLastWidth)
  256. mFirstPoint = new ControlPoint(x, y, mLastWidth)
  257. nowList.push(curPoint)
  258. lastForce = force
  259. }
  260. const penMove = (x, y, force) => {
  261. let pressure = calculatePressure(force)
  262. let pressureCheck = forceCreck(lastForce, pressure)
  263. lastForce = pressureCheck
  264. let curWidth = pressureCheck / transFormScale * penWidth
  265. curPoint = new ControlPoint(x, y, curWidth)
  266. let curDis = Math.hypot(curPoint.x - mLastPoint.x, curPoint.y - mLastPoint.y)
  267. if (pointNum === 1) {
  268. pointNum++;
  269. mBezier.initPoint(mLastPoint, curPoint);
  270. } else {
  271. mBezier.addNodePoint(curPoint);
  272. }
  273. mLastWidth = curWidth;
  274. doMove(curDis);
  275. mLastWidth = new ControlPoint(curPoint.x, curPoint.y, curPoint.width);
  276. }
  277. function penUp(x, y, force, context) {
  278. if (nowList.length === 0) {
  279. return
  280. }
  281. curPoint = new ControlPoint(x, y, 0)
  282. let deltaX = curPoint.x - mLastPoint.x
  283. let deltaY = curPoint.y - mLastPoint.y
  284. let curDis = Math.hypot(deltaX, deltaY)
  285. mBezier.addNodePoint(curPoint)
  286. let steps = 1 + Math.floor((curDis / 10))
  287. let step = 1 / steps
  288. for (let t = 0; t < 1; t += step) {
  289. let point = mBezier.getPoint(t)
  290. nowList.push(point)
  291. }
  292. mBezier.penEnd()
  293. for (let t = 0; t < 1; t += step) {
  294. let point = mBezier.getPoint(t)
  295. nowList.push(point)
  296. }
  297. draws(context);
  298. nowList = []
  299. }
  300. function doMove(curDis) {
  301. let steps = 1 + Math.floor((curDis / 10));
  302. let step = 1 / steps
  303. for (let t = 0; t < 1; t += step) {
  304. let Point = mBezier.getPoint(t)
  305. nowList.push(Point)
  306. }
  307. }
  308. function draws(context) {
  309. doPreDraw(context);
  310. }
  311. function doPreDraw(context) {
  312. let curPoint = nowList[0]
  313. let length = nowList.length
  314. for (let i = 1; i < length; i++) {
  315. drawPoint(curPoint, nowList[i], context)
  316. curPoint = nowList[i]
  317. }
  318. }
  319. function drawPoint(curPoint, point, context) {
  320. if (curPoint.x === point.x && curPoint.y === point.y) {
  321. return
  322. }
  323. drawLine(curPoint.x, curPoint.y, curPoint.width, point.x, point.y, point.width, context);
  324. }
  325. function drawLine(x0, y0, w0, x1, y1, w1, context) {
  326. // console.log(x0, y0, w0, x1, y1, w1)
  327. let curDis = Math.hypot(x1 - x0, y1 - y0)
  328. let step = 1
  329. if (penWidth <= 6) {
  330. step = 1 + Math.floor((curDis))
  331. } else if (penWidth > 60) {
  332. step = 1 + Math.floor((curDis / 4))
  333. } else {
  334. step = 1 + Math.floor((curDis / 3))
  335. }
  336. let deltaX = (x1 - x0) / step
  337. let deltaY = (y1 - y0) / step
  338. let deltaW = (w1 - w0) / step
  339. let x = x0
  340. let y = y0
  341. let w = w0
  342. // console.log(context)
  343. for (let i = 0; i < step; i++) {
  344. let left = x + w / 2
  345. let top = y + w / 2
  346. let right = x - w / 2
  347. let bottom = y - w / 2
  348. let midPointX = (left + right) / 2
  349. let midPointY = (top + bottom) / 2
  350. let xRadius = Math.abs((left - right) / 2)
  351. let yRadius = Math.abs((top - bottom) / 2)
  352. context.setLineDash([])
  353. context.beginPath();
  354. context.ellipse(midPointX * 0.97, midPointY * 0.976, xRadius, yRadius, 0, 0, Math.PI * 2)
  355. context.stroke();
  356. context.closePath()
  357. context.fill()
  358. x += deltaX
  359. y += deltaY
  360. w += deltaW
  361. }
  362. }
  363. // 压力值前后差距过大补正
  364. const forceCreck = (lastForce, curForce) => {
  365. if ((curForce - lastForce) > 35) {
  366. return (lastForce + curForce) / 2
  367. }
  368. return curForce
  369. }
  370. export {
  371. Stroke
  372. }