import toast from '../miniprogram_npm/@vant/weapp/toast/toast'; import { request } from "../utils/api" import { env } from "../env" import smooth from '../tqlsdk/filter' const event = require('../tqlsdk/event') const app = getApp() class myCanvas { constructor(canvasId, canvasWidth, canvasHeight, imgUrl) { this.canvas = null this.canvasNode = null this.canvas = null this.canvasNode = null this.ctx = null this.allImage = {} this.force = 0 this.canvasWidth = canvasWidth this.canvasHeight = canvasHeight this.mapData = new Map(); this.x_coordinate = null this.y_coordinate = null this.color = '#141414' this.isDown = false this.lastPage = null this.isReplay = false this.p_index = 0 this.scale = 1 this.offsetX = 0 this.offsetY = 0 this.lineWidth = 2 this.penWidth = 1 this.strokeFlag = false this.lastPoint = null this.imgUrls = null this.offlineFlag = false this.readLocalFileFlag = false //实际宽高 this.actualWidth = 182 this.actualHeight = 256 this.codePointDistance_x = 1.524 this.codePointDistance_y = 1.524 this.initCanvas(canvasId, canvasWidth, canvasHeight, imgUrl) } //设置实际大小 setActualSize(width, height) { this.actualWidth = width this.actualHeight = height } //设置码点类型 009 011 setCodePointType(codeType) { switch (codeType) { case 12: this.codePointDistance_x = 1.524 this.codePointDistance_y = 1.524 break; case 16: this.codePointDistance_x = 2.013 this.codePointDistance_y = 2.013 break; default: this.codePointDistance_x = 1.524 this.codePointDistance_y = 1.524 break; } } // 开关笔锋 /** * * @param {boolean} flag */ changeStroke(flag) { this.strokeFlag = flag } // 改变笔迹宽度 /** * * @param {number} width */ changeWidth(width) { this.penWidth = width } // 初始化canvas /** * @param {string} canvasId * @param {number} canvasWidth * @param {number} canvasHeight * @param {Array} imgUrl */ initCanvas(canvasId, canvasWidth, canvasHeight, imgUrl) { console.log(canvasId, canvasWidth, canvasHeight, imgUrl) wx.createSelectorQuery().select(canvasId).fields({ node: true, size: true }).exec(res => { this.canvas = res[0].node this.ctx = this.canvas.getContext('2d') this.canvas.width = canvasWidth * 2 this.canvas.height = canvasHeight * 2 this.imgUrls = imgUrl console.log(this.imgUrls) this.ctx.lineCap = "round" this.ctx.lineJoin = "miter" this.ctx.miterLimit = 1 this.changePage(0) }) } saveCanvasImage(homeworkId, studentId, type, subject = '') { console.log('保存图片!!'); setTimeout(() => { const base64 = this.canvas.toDataURL("png"); let body = {} body.homeworkId = homeworkId body.studentId = studentId body.baseStr = base64.substring(22) body.subject = subject console.log(body) if (type === "teacher") { wx.request({ url: env.baseUrl + '/app-api/tutor/h5/teacherUpload', method: 'post', data: { baseStr: base64.substring(22), id: homeworkId }, header: { 'content-type': 'application/x-www-form-urlencoded' // 默认值 }, success(res) { console.log(res.data) if (res.data.code == 0) { toast.success({ message: "保存成功", duration: 1500, }) } } }) } else { wx.request({ url: env.baseUrl + '/app-api/tutor/h5/studentUpload', method: 'post', data: body, header: { 'content-type': 'application/x-www-form-urlencoded' // 默认值 }, success(res) { console.log(res.data) if (res.data.code == 0) { toast.success({ message: "保存成功", duration: 1500, }) } } }) } }, 200) } // 改变颜色 /** * * @param {string} color */ changeColor(color) { this.color = color this.ctx.fillStyle = color; this.ctx.strokeStyle = color; } // 绘制图片 /** * * @param {string} imgUrl * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 */ async drawImage(imgUrl, x1, y1, x2, y2) { if (!this.allImage[imgUrl]) { this.allImage[imgUrl] = await new Promise((resolve) => { const img = this.canvas.createImage() img.src = imgUrl img.onload = () => { resolve(img) } }) } this.ctx.drawImage(this.allImage[imgUrl], x1, y1, x2, y2) } // 切页 /** * * @param {number} index */ async changePage(index) { if (index === this.lastPage && !this.isReplay) { return } // 此处切页 event.emit('changePage', index) this.lastPage = index await this.drawImage(this.imgUrls[(index & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2) // 切页绘制 if (!this.isReplay) { let pageData = this.mapData.get(index) if (!pageData) { return } let pageLength = pageData.length for (let i = 0; i < pageLength; i++) { 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) } } } // 清除 async clear() { this.mapData.clear(); this.ctx.clearRect(0, 0, this.canvasWidth * 2, this.canvasHeight * 2) // 清除后重新绘制底图 this.addPageId(this.lastPage) await this.drawImage(this.imgUrls[(this.lastPage & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2) } // 回放 async replay() { this.isReplay = true this.ctx.clearRect(0, 0, this.canvasWidth * 2, this.canvasHeight * 2) // 清除后重新绘制底图 this.drawImage(this.imgUrls[(this.lastPage & 1 ? 1 : 0)], 0, 0, this.canvasWidth * 2, this.canvasHeight * 2) if (!this.mapData.has(this.lastPage) || this.mapData.get(this.lastPage).length === 0) { this.isReplay = false return } let currentPageData = this.mapData.get(this.lastPage) let dataLength = currentPageData.length for (let index = 0; index < dataLength; index++) { await sleep(10) 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) } this.isReplay = false } // 储存数据 addPageId(myPageId) { if (myPageId === this.lastPage) { !this.mapData.has(myPageId) && this.mapData.set(myPageId, []) return }!this.mapData.has(myPageId) && this.mapData.set(myPageId, []) this.changePage(myPageId); } // 四舍五入算法 roundNum(number, fractionDigits) { return Math.round(number * Math.pow(10, fractionDigits)) / Math.pow(10, fractionDigits); } // 数据处理 usbData(data) { // 回放时不接收数据 if (this.isReplay) { toast.fail('回放时请不要书写') return } // let newDot = smooth(data) let newDot = data // console.log(newDot); if (newDot.dotType === "PEN_DOWN") { // 切页 this.addPageId(data.pageID) } // switch (data.bookID) { // case value: // break; // default: // break; // } // console.log((data.x) + ((data.fx) / 100)) // console.log((data.y) + ((data.fy) / 100)) // let x = (data.x) + data.fx / 100 // let y = (data.y) + data.fy / 100 // 得到的x,y坐标 因为此处canvas为2倍放大 所以图标宽度也需要乘以2 // let xPoint = this.roundNum(((((data.x) + ((data.fx) / 100)) * this.canvasWidth * 2) / ax), 13); // console.log(xPoint); // let yPoint = this.roundNum(((((data.y) + ((data.fy) / 100)) * this.canvasHeight * 2) / ay), 13); // const x = (_x * 840) / (210 / 1.524) // const y = (_y * 1188) / (297 / 1.524) let xPoint = this.roundNum(((newDot.ab_x * this.canvasWidth * 2) / (this.actualWidth / this.codePointDistance_x)), 13); let yPoint = this.roundNum(((newDot.ab_y * this.canvasHeight * 2) / (this.actualHeight / this.codePointDistance_y)), 13); // console.log(yPoint); // console.log('转换前的坐标', x, y) // console.log('转换后的坐标', xPoint, yPoint) this.mapData.get(newDot.pageID).push({ bookId: newDot.bookID, pageID: newDot.pageID, timeLog: newDot.timeLong, xPoint, yPoint, dotType: newDot.dotType, force: newDot.force, color: this.color }) this.strokeFlag ? this.strokeType(newDot.dotType, xPoint, yPoint, data.force) : this.penType(newDot.dotType, xPoint, yPoint) } // 点分类型(有笔锋) strokeType(dotType, x, y, force) { // console.log(x, y, force); 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) } // 点分类型(无笔锋) penType(dotType, x, y) { dotType === 'PEN_DOWN' ? this.penDown(x, y) : dotType === 'PEN_MOVE' ? this.penMove(x, y) : this.penUp(x, y) } // down点方法(无笔锋) penDown(x, y) { this.lastPoint = { x, y } } // move点方法(无笔锋) penMove(x, y) { let newPoint = { x, y } this.drawLine(this.lastPoint.x, this.lastPoint.y, newPoint.x, newPoint.y); this.lastPoint = newPoint; } // up点方法 penUp(x_0, y_0) { this.drawLine(this.lastPoint.x, this.lastPoint.y, x_0, y_0) } // 绘制方法(无笔锋) drawLine(x1, y1, x2, y2) { this.ctx.beginPath(); this.ctx.moveTo(x1, y1); this.ctx.lineWidth = this.penWidth * 2 this.ctx.lineTo(x2, y2); this.ctx.fill(); this.ctx.stroke(); this.ctx.closePath(); } // down点方法(有笔锋) strokeDown(x, y, force, width) { // console.log(123) penStrokeDown(x, y, force, width) } // move点方法(有笔锋) strokeMove(x, y, force, width) { penStrokeMove(x, y, force, width) } // up点方法(有笔锋) strokeUp(x, y, force, ctx, penWidth) { penStrokeUp(x, y, force, ctx, penWidth) } } // 控制点类 class ControlPoint { constructor(x, y, width) { this.x = x this.y = y this.width = width } set(x, y, width) { this.x = x this.y = y this.width = width } setControlPoint(point) { this.x = point.x this.y = point.y this.width = point.width } } // 贝塞尔类 class Bezier { constructor() { // 控制点 this.mControl = new ControlPoint(); // 距离 this.mDestination = new ControlPoint() // 下一个需要控制点 this.mNextControl = new ControlPoint() // 资源的点 this.mSource = new ControlPoint() } initPoint(last, cur) { this.init(last.x, last.y, last.width, cur.x, cur.y, cur.width) } init(lastX, lastY, lastWidth, x, y, width) { //资源点设置 最后的点位资源点 this.mSource.set(lastX, lastY, lastWidth) let xMid = getMid(lastX, x) let yMid = getMid(lastY, y) let wMid = getMid(lastWidth, width) // 距离单为平均点 this.mDestination.set(xMid, yMid, wMid) // 控制点为当前的距离点 this.mControl.set(getMid(lastX, xMid), getMid(lastY, yMid), getMid(lastWidth, wMid)) // 下个控制点为当前点 this.mNextControl.set(x, y, width) } addNodePoint(cur) { this.addNode(cur.x, cur.y, cur.width) } //替换旧的点 原来的距离点变换为资源点,控制点变为原来的下一个控制点,距离点取原来控制点的和新的的一半 下个控制点为新的点 addNode(x, y, width) { this.mSource.setControlPoint(this.mDestination) this.mControl.setControlPoint(this.mNextControl) this.mDestination.set(getMid(this.mNextControl.x, x), getMid(this.mNextControl.y, y), getMid(this.mNextControl.width, width)) this.mNextControl.set(x, y, width); } //结束时改变点的位置 penEnd() { this.mSource.setControlPoint(this.mDestination) this.mControl.set(getMid(this.mNextControl.x, this.mSource.x), getMid(this.mNextControl.y, this.mSource.y), getMid(this.mNextControl.width, this.mSource.width)) this.mDestination.setControlPoint(this.mNextControl) } // 获取点的信息 getPoint(t) { let point = new ControlPoint() point.set(this.getX(t), this.getY(t), this.getW(t)) return point } //三阶曲线控制点 getValue(p0, p1, p2, t) { let a = p2 - 2 * p1 + p0 let b = 2 * (p1 - p0) return a * t * t + b * t + p0 } getX(t) { return this.getValue(this.mSource.x, this.mControl.x, this.mDestination.x, t) } getY(t) { return this.getValue(this.mSource.y, this.mControl.y, this.mDestination.y, t) } getW(t) { return getWidth(this.mSource.width, this.mDestination.width, t) } } // 计算压力值 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; // 获取中间值 const getMid = (x, y) => (x + y) / 2 // 计算宽度 const getWidth = (w0, w1, t) => w0 + (w1 - w0) * t // 当前点 let curPoint = null // 上一个点 let mLastPoint = null // 计算出来的线段宽度 let mLastWidth = null // 贝塞尔类实例 let mBezier = new Bezier() // 笔画的第一点 let mFirstPoint = null // 点击数 let pointNum = 0 //转换参数 const transFormScale = 80 // 每笔的数据 let nowList = [] // 上一压力值 let lastForce = null // down点(笔锋) const penStrokeDown = (x, y, force, width) => { let pressure = calculatePressure(force) mLastWidth = pressure / transFormScale * width pointNum = 1 // 记录down点信息 curPoint = new ControlPoint(x, y, mLastWidth) mLastPoint = new ControlPoint(x, y, mLastWidth) // console.log(mLastPoint) mFirstPoint = new ControlPoint(x, y, mLastWidth) nowList = [] nowList.push(curPoint) lastForce = force } // move点方法(笔锋) const penStrokeMove = (x, y, force, penWidth) => { let pressure = calculatePressure(force) let pressureCheck = forceCreck(lastForce, pressure) lastForce = pressureCheck let curWidth = pressureCheck / transFormScale * penWidth curPoint = new ControlPoint(x, y, curWidth) // console.log(curPoint, mLastPoint) let curDis = Math.hypot(curPoint.x - mLastPoint.x, curPoint.y - mLastPoint.y) if (pointNum === 1) { pointNum++; mBezier.initPoint(mLastPoint, curPoint); } else { mBezier.addNodePoint(curPoint); } mLastWidth = curWidth; doMove(curDis); mLastWidth = new ControlPoint(curPoint.x, curPoint.y, curPoint.width); // console.log(mFirstPoint); } // up点方法(笔锋) const penStrokeUp = (x, y, force, context, penWidth) => { if (nowList.length === 0) { return } curPoint = new ControlPoint(x, y, 0) let deltaX = curPoint.x - mLastPoint.x let deltaY = curPoint.y - mLastPoint.y let curDis = Math.hypot(deltaX, deltaY) mBezier.addNodePoint(curPoint) let steps = 1 + Math.floor((curDis / 10)) let step = 1 / steps for (let t = 0; t < 1; t += step) { let point = mBezier.getPoint(t) nowList.push(point) } mBezier.penEnd() for (let t = 0; t < 1; t += step) { let point = mBezier.getPoint(t) nowList.push(point) } draws(context, penWidth); nowList = [] } function doMove(curDis) { let steps = 1 + Math.floor((curDis / 10)); let step = 1 / steps for (let t = 0; t < 1; t += step) { let Point = mBezier.getPoint(t) nowList.push(Point) } } function draws(context, penWidth) { doPreDraw(context, penWidth); } function doPreDraw(context, penWidth) { let curPoint = nowList[0] let length = nowList.length for (let i = 1; i < length; i++) { drawPoint(curPoint, nowList[i], context, penWidth) curPoint = nowList[i] } } function drawPoint(curPoint, point, context, penWidth) { // 相同点不绘制 if (curPoint.x === point.x && curPoint.y === point.y) { return } drawLine(curPoint.x, curPoint.y, curPoint.width, point.x, point.y, point.width, context, penWidth); } // 绘制方法 function drawLine(x0, y0, w0, x1, y1, w1, context, penWidth) { let curDis = Math.hypot(x1 - x0, y1 - y0) let step = 1 if (penWidth <= 6) { step = 1 + Math.floor((curDis)) } else if (penWidth > 60) { step = 1 + Math.floor((curDis / 4)) } else { step = 1 + Math.floor((curDis / 3)) } let deltaX = (x1 - x0) / step let deltaY = (y1 - y0) / step let deltaW = (w1 - w0) / step let x = x0 let y = y0 let w = w0 for (let i = 0; i < step; i++) { let left = x + w / 2 let top = y + w / 2 let right = x - w / 2 let bottom = y - w / 2 let midPointX = (left + right) / 2 let midPointY = (top + bottom) / 2 let xRadius = Math.abs((left - right) / 2) let yRadius = Math.abs((top - bottom) / 2) context.setLineDash([]) context.beginPath(); context.ellipse(midPointX * 0.97, midPointY * 0.976, xRadius, yRadius, 0, 0, Math.PI * 2); context.stroke(); context.closePath(); context.fill(); x += deltaX y += deltaY w += deltaW } } // 压力值前后差距过大补正 const forceCreck = (lastForce, curForce) => { if ((curForce - lastForce) > 35) { return (lastForce + curForce) / 2 } return curForce } // 休眠方法 function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } export { myCanvas }